@c4a/context-cli 0.7.14 → 0.7.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli.js CHANGED
@@ -41982,6 +41982,12 @@ var getSourceType = (sourceDefinition) => {
41982
41982
  }), captureLark = (definition) => {
41983
41983
  const sourceDefinition = bindSourceType(definition.source, "lark", "captureLark source");
41984
41984
  const sourceId = getSourceName(sourceDefinition);
41985
+ for (const key of ["images", "gifs"]) {
41986
+ const value = definition.resources?.[key];
41987
+ if (value !== undefined && value !== "bundle" && value !== "reference-only") {
41988
+ throw new TypeError(`captureLark resources.${key} must be bundle or reference-only`);
41989
+ }
41990
+ }
41985
41991
  const maxBytesPerResource = definition.resources?.maxBytesPerResource ?? 20 * 1024 * 1024;
41986
41992
  const maxTotalBytes = definition.resources?.maxTotalBytes ?? 200 * 1024 * 1024;
41987
41993
  if (!Number.isSafeInteger(maxBytesPerResource) || maxBytesPerResource < 1) {
@@ -42000,6 +42006,8 @@ var getSourceType = (sourceDefinition) => {
42000
42006
  writes: [sourceSnapshotResource(sourceDefinition, "lark")],
42001
42007
  source: sourceDefinition,
42002
42008
  resources: {
42009
+ ...definition.resources?.images === undefined ? {} : { images: definition.resources.images },
42010
+ ...definition.resources?.gifs === undefined ? {} : { gifs: definition.resources.gifs },
42003
42011
  videos: definition.resources?.videos ?? "reference-only",
42004
42012
  maxBytesPerResource,
42005
42013
  maxTotalBytes
@@ -64786,6 +64794,8 @@ Restore the authorized source and retry preparation.
64786
64794
  ...scopes.map((scope2) => `- ${scope2.scope}: ${join25(directory, productionSourceFile(scope2.scope))}`),
64787
64795
  "",
64788
64796
  "Use code skeletons and document outlines to identify the authorized capability families and document tasks, then selectively read full material to decide reader topics. Navigation is not a complete feature inventory. Keep unchecked scope pending; do not parse all code or maintain per-symbol disposition just to plan.",
64797
+ "Configured sources are the knowledge workspace coverage boundary, not a new investigation assignment on every request. First distinguish the user's current task, its actual source dependencies, and unrelated configured sources. Reuse approved content; a source-level pending entry alone does not prove missing knowledge or require new articles.",
64798
+ "Report source baseline/read failures separately from content gaps. For an unrelated configured source, explain that its availability check is unresolved outside this task; do not promise a new code investigation. If the Route still requires resolution, report that precise workflow limitation without deleting source configuration, clearing runtime state, or claiming the source was investigated.",
64789
64799
  "Scale planning to the current request. For one or two documents or a clearly bounded module, decide which articles to add or revise and where they belong; do not redesign the whole knowledge base. Start with related existing topics, expand reading only when needed, and reuse applicable decisions. A large module may need several topics, but not investigation of unrelated modules.",
64790
64800
  "Separate the whole requested outcome from the current writing batch. Use one batch unless actual dependencies or useful parallel work justify more; do not invent page counts or dependencies. A first useful delivery does not settle remaining authorized work.",
64791
64801
  "Use question and brief to describe the reader task and useful depth: a checked file/symbol or document section with a concrete next step for navigation, or the behavior, conditions and steps needed for explanation. Reuse or revise existing articles without replacing valid detail with generic summaries; split distinct tasks, not sources or symbols.",
@@ -68458,7 +68468,7 @@ function runtimeEventPendingAgentHint(result) {
68458
68468
  requires_network_access: true,
68459
68469
  plan_command: "context logs plan --format json",
68460
68470
  command: "context logs flush --format json",
68461
- message: "Runtime logs are queued locally. Read the fixed delivery plan, request network access with its audit details, and run only its flush command before handing off the completed step."
68471
+ message: "Runtime logs are queued locally. If delivery is already authorized and the destination is unchanged, run context logs flush --format json directly. Use the delivery plan when the destination or required host network permission is not yet established."
68462
68472
  };
68463
68473
  }
68464
68474
  function queueContextRuntimeEvent(input) {
@@ -70284,6 +70294,9 @@ function formatPackageBuildSummary(pkg) {
70284
70294
  lines.push(` warning: ${warning.path} references ${warning.target}, which ${explanation}.`);
70285
70295
  }
70286
70296
  const optimization = pkg.resources.delivery.optimization;
70297
+ for (const warning of optimization?.warnings ?? []) {
70298
+ lines.push(` image replaced with placeholder: ${warning.path}: ${warning.reason}`);
70299
+ }
70287
70300
  if (optimization?.state === "applied") {
70288
70301
  lines.push(` asset optimization: ${optimization.processor}/${optimization.mode}, saved ${optimization.savedBytes} byte(s), largest ${optimization.largestOutputBytes}/${optimization.maxImageBytes} byte(s), total ${optimization.outputBytes}/${optimization.maxTotalImageBytes} byte(s)`);
70289
70302
  } else if (pkg.resources.delivery.state === "git-raw") {
@@ -70766,21 +70779,23 @@ var siteMarkdownConfig, siteThemeScript, siteThemeCss = `
70766
70779
  .VPNavBarSearch .DocSearch-Button-Container { display: flex; flex: 1; min-width: 0; align-items: center; }
70767
70780
  .VPNavBarSearch .DocSearch-Button-Keys { margin-left: auto; }
70768
70781
  .VPSidebar { border-top: 1px solid var(--vp-c-divider); border-right: 1px solid var(--vp-c-divider); scrollbar-width: thin; }
70769
- .VPSidebar .group + .group { border: 0; padding-top: 8px; }
70782
+ .VPSidebar .group, .VPSidebar .group + .group { border: 0; padding-top: 0; }
70770
70783
  .VPSidebarItem .link { min-width: 0; overflow: hidden; }
70771
70784
  .VPSidebarItem .text { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 13px !important; line-height: 20px !important; font-weight: 450 !important; }
70772
- .VPSidebarItem.level-0 { padding-bottom: 8px !important; }
70785
+ .VPSidebarItem.level-0 { padding-bottom: 0 !important; }
70773
70786
  .VPSidebarItem .indicator { display: none; }
70774
- .VPSidebarItem .text { padding: 3px 0 !important; }
70775
- .VPSidebarItem .item { min-height: 32px; padding: 3px 16px; border-radius: 0; }
70787
+ .VPSidebarItem .text { padding: 0 !important; }
70788
+ .VPSidebarItem .item { height: 38px; min-height: 38px; align-items: center; padding: 0 16px 0 26px; border-radius: 0; }
70789
+ .VPSidebarItem.is-link:not(.is-active) > .item > .link > p.text { color: var(--vp-c-text-2); }
70790
+ .VPSidebarItem.is-link:not(.is-active) > .item > .link:hover > p.text { color: var(--vp-c-brand-1); }
70776
70791
  .VPSidebarItem.is-active > .item { background: var(--vp-c-brand-soft); box-shadow: none; }
70777
70792
  .VPSidebarItem.is-active > .item .text { color: var(--vp-c-brand-1) !important; font-weight: 550 !important; }
70778
70793
  .VPSidebarItem .items { margin-left: 0; padding-left: 0 !important; border-left: 0 !important; }
70779
- .VPSidebarItem.level-1 > .item { padding-left: 30px; }
70780
- .VPSidebarItem.level-2 > .item { padding-left: 44px; }
70781
- .VPSidebarItem.level-3 > .item { padding-left: 58px; }
70782
- .VPSidebarItem.level-4 > .item { padding-left: 72px; }
70783
- .VPSidebarItem.level-5 > .item { padding-left: 86px; }
70794
+ .VPSidebarItem.level-1 > .item { padding-left: 40px; }
70795
+ .VPSidebarItem.level-2 > .item { padding-left: 54px; }
70796
+ .VPSidebarItem.level-3 > .item { padding-left: 68px; }
70797
+ .VPSidebarItem.level-4 > .item { padding-left: 82px; }
70798
+ .VPSidebarItem.level-5 > .item { padding-left: 96px; }
70784
70799
  .VPSidebar .group { width: 100% !important; }
70785
70800
  .VPSidebarItem .item:hover { background: var(--vp-c-default-soft); }
70786
70801
  .VPDoc .container, .VPDoc > .container > .content, .VPDoc .content-container { max-width: none !important; min-width: 0 !important; }
@@ -71511,15 +71526,23 @@ function sidebar(entries2) {
71511
71526
  return entries2.map((entry) => ({
71512
71527
  text: entry.title,
71513
71528
  ...entry.href ? { link: entry.href } : {},
71514
- ...entry.children.length ? { collapsed: true, items: sidebar(entry.children) } : {}
71529
+ items: sidebar(entry.children),
71530
+ ...entry.children.length ? { collapsed: true } : {}
71515
71531
  }));
71516
71532
  }
71517
71533
  function siteSections(entries2) {
71534
+ const directoriesFirst = (siblings) => {
71535
+ const ordered = [
71536
+ ...siblings.filter((entry) => entry.children.length > 0),
71537
+ ...siblings.filter((entry) => entry.children.length === 0)
71538
+ ];
71539
+ return ordered.map((entry) => ({ ...entry, children: directoriesFirst(entry.children) }));
71540
+ };
71518
71541
  const pageLinks = (entry) => [
71519
71542
  ...entry.href ? [entry.href.split("#")[0]] : [],
71520
71543
  ...entry.children.flatMap(pageLinks)
71521
71544
  ];
71522
- return entries2.map((entry) => ({
71545
+ return entries2.map((entry) => ({ ...entry, children: directoriesFirst(entry.children) })).map((entry) => ({
71523
71546
  key: entry.key,
71524
71547
  title: entry.title,
71525
71548
  href: `/sections/${createHash16("sha256").update(entry.key).digest("hex").slice(0, 20)}.html`,
@@ -72176,7 +72199,11 @@ async function loadSharpProcessor() {
72176
72199
  }
72177
72200
  return {
72178
72201
  async optimize(bytes, definition2) {
72179
- let pipeline2 = sharp(bytes, { failOn: "error", animated: true }).rotate();
72202
+ const image = sharp(bytes, { failOn: "error", animated: false });
72203
+ const metadata = await image.metadata();
72204
+ if ((metadata.pages ?? 1) > 1)
72205
+ return bytes;
72206
+ let pipeline2 = image.rotate();
72180
72207
  if (definition2.maxDimension !== undefined) {
72181
72208
  pipeline2 = pipeline2.resize({
72182
72209
  width: definition2.maxDimension,
@@ -72200,6 +72227,8 @@ async function adaptiveVariants(input) {
72200
72227
  let smallest = input.asset.bytes.byteLength;
72201
72228
  for (const definition2 of input.definitions) {
72202
72229
  const output = await input.processor.optimize(input.asset.bytes, definition2);
72230
+ if (output === input.asset.bytes)
72231
+ break;
72203
72232
  if (!isWebp(output)) {
72204
72233
  throw new ContextError(ExitCode.WorkspaceStateError, "package asset optimizer returned invalid WebP bytes", {
72205
72234
  category: ErrorCategory.WorkspaceStateInvalid,
@@ -72215,17 +72244,6 @@ async function adaptiveVariants(input) {
72215
72244
  }
72216
72245
  return variants;
72217
72246
  }
72218
- function budgetError(input) {
72219
- return new ContextError(ExitCode.WorkspaceStateError, "bundled images cannot meet the package size budget", {
72220
- category: ErrorCategory.WorkspaceStateInvalid,
72221
- reason_code: "package.assets.image-budget-exceeded",
72222
- output_bytes: input.outputBytes,
72223
- max_image_bytes: input.maxImageBytes,
72224
- max_total_image_bytes: input.maxTotalImageBytes,
72225
- oversized_paths: input.oversized.map((asset) => asset.packageRelPath),
72226
- next: "Reduce or replace the reported source images, then rerun context build."
72227
- });
72228
- }
72229
72247
  async function optimizePackageAssetFiles(input) {
72230
72248
  const maxImageBytes = input.maxImageBytes ?? PACKAGE_ASSET_MAX_IMAGE_BYTES;
72231
72249
  const maxTotalImageBytes = input.maxTotalImageBytes ?? PACKAGE_ASSET_MAX_TOTAL_IMAGE_BYTES;
@@ -72251,23 +72269,38 @@ async function optimizePackageAssetFiles(input) {
72251
72269
  const processor = input.processor ?? await loadSharpProcessor();
72252
72270
  const definitions = input.definition === undefined ? DEFAULT_OPTIMIZATION_PROFILES : [input.definition];
72253
72271
  const variantsByPath = new Map;
72272
+ const omittedImages = new Set;
72273
+ const warnings = [];
72254
72274
  for (const asset of candidates) {
72255
- variantsByPath.set(asset.packageRelPath, await adaptiveVariants({ asset, processor, definitions }));
72275
+ try {
72276
+ variantsByPath.set(asset.packageRelPath, await adaptiveVariants({ asset, processor, definitions }));
72277
+ } catch (error) {
72278
+ if (error instanceof ContextError)
72279
+ throw error;
72280
+ warnings.push({ path: asset.packageRelPath, reason: error instanceof Error ? error.message : String(error) });
72281
+ omittedImages.add(asset.packageRelPath);
72282
+ variantsByPath.set(asset.packageRelPath, [{ bytes: asset.bytes }]);
72283
+ }
72256
72284
  }
72257
72285
  const selectedIndex = new Map;
72258
72286
  for (const asset of candidates) {
72259
72287
  const variants = variantsByPath.get(asset.packageRelPath);
72288
+ if (omittedImages.has(asset.packageRelPath))
72289
+ continue;
72260
72290
  let index2 = input.definition !== undefined && variants.length > 1 ? 1 : 0;
72261
72291
  if (asset.bytes.byteLength > maxImageBytes) {
72262
72292
  const fitting = variants.findIndex((variant) => variant.bytes.byteLength <= maxImageBytes);
72263
72293
  if (fitting < 0) {
72264
- throw budgetError({ outputBytes: originalBytes, maxImageBytes, maxTotalImageBytes, oversized: [asset] });
72294
+ omittedImages.add(asset.packageRelPath);
72295
+ warnings.push({ path: asset.packageRelPath, reason: "Image exceeds the per-image delivery budget after optimization" });
72265
72296
  }
72266
- index2 = fitting;
72297
+ index2 = Math.max(0, fitting);
72267
72298
  }
72268
72299
  selectedIndex.set(asset.packageRelPath, index2);
72269
72300
  }
72270
72301
  const selectedBytes = (asset) => {
72302
+ if (omittedImages.has(asset.packageRelPath))
72303
+ return new Uint8Array;
72271
72304
  const variants = variantsByPath.get(asset.packageRelPath);
72272
72305
  return variants[selectedIndex.get(asset.packageRelPath) ?? 0].bytes;
72273
72306
  };
@@ -72275,6 +72308,8 @@ async function optimizePackageAssetFiles(input) {
72275
72308
  while (outputBytes > maxTotalImageBytes) {
72276
72309
  let best;
72277
72310
  for (const asset of candidates) {
72311
+ if (omittedImages.has(asset.packageRelPath))
72312
+ continue;
72278
72313
  const variants = variantsByPath.get(asset.packageRelPath);
72279
72314
  const currentIndex = selectedIndex.get(asset.packageRelPath) ?? 0;
72280
72315
  const nextIndex = currentIndex + 1;
@@ -72286,7 +72321,13 @@ async function optimizePackageAssetFiles(input) {
72286
72321
  best = { asset, nextIndex, saving };
72287
72322
  }
72288
72323
  if (best === undefined) {
72289
- throw budgetError({ outputBytes, maxImageBytes, maxTotalImageBytes, oversized: [] });
72324
+ const largest = candidates.filter((asset) => !omittedImages.has(asset.packageRelPath)).sort((a, b) => selectedBytes(b).byteLength - selectedBytes(a).byteLength || a.packageRelPath.localeCompare(b.packageRelPath))[0];
72325
+ if (largest === undefined)
72326
+ break;
72327
+ outputBytes -= selectedBytes(largest).byteLength;
72328
+ omittedImages.add(largest.packageRelPath);
72329
+ warnings.push({ path: largest.packageRelPath, reason: "Image exceeds the total delivery budget after optimization" });
72330
+ continue;
72290
72331
  }
72291
72332
  selectedIndex.set(best.asset.packageRelPath, best.nextIndex);
72292
72333
  outputBytes -= best.saving;
@@ -72294,6 +72335,8 @@ async function optimizePackageAssetFiles(input) {
72294
72335
  const optimizedByInputPath = new Map;
72295
72336
  const optimizedTargetByOriginal = new Map;
72296
72337
  for (const asset of candidates) {
72338
+ if (omittedImages.has(asset.packageRelPath))
72339
+ continue;
72297
72340
  const output = selectedBytes(asset);
72298
72341
  if (output.byteLength >= asset.bytes.byteLength)
72299
72342
  continue;
@@ -72301,14 +72344,14 @@ async function optimizePackageAssetFiles(input) {
72301
72344
  optimizedByInputPath.set(asset.packageRelPath, { ...asset, packageRelPath, bytes: output });
72302
72345
  optimizedTargetByOriginal.set(asset.packageRelPath, packageRelPath);
72303
72346
  }
72304
- const assets = input.assets.map((asset) => optimizedByInputPath.get(asset.packageRelPath) ?? asset);
72305
- outputBytes = candidates.reduce((sum, asset) => sum + (optimizedByInputPath.get(asset.packageRelPath)?.bytes.byteLength ?? asset.bytes.byteLength), 0);
72306
- const largestOutputBytes = Math.max(0, ...candidates.map((asset) => optimizedByInputPath.get(asset.packageRelPath)?.bytes.byteLength ?? asset.bytes.byteLength));
72347
+ const assets = input.assets.filter((asset) => !omittedImages.has(asset.packageRelPath)).map((asset) => optimizedByInputPath.get(asset.packageRelPath) ?? asset);
72348
+ outputBytes = candidates.filter((asset) => !omittedImages.has(asset.packageRelPath)).reduce((sum, asset) => sum + (optimizedByInputPath.get(asset.packageRelPath)?.bytes.byteLength ?? asset.bytes.byteLength), 0);
72349
+ const largestOutputBytes = Math.max(0, ...candidates.filter((asset) => !omittedImages.has(asset.packageRelPath)).map((asset) => optimizedByInputPath.get(asset.packageRelPath)?.bytes.byteLength ?? asset.bytes.byteLength));
72307
72350
  return {
72308
72351
  assets,
72309
72352
  optimizedTargetByOriginal,
72310
72353
  summary: {
72311
- state: optimizedByInputPath.size > 0 ? "applied" : "configured-no-benefit",
72354
+ state: omittedImages.size > 0 ? "partial" : optimizedByInputPath.size > 0 ? "applied" : "configured-no-benefit",
72312
72355
  candidateFiles: candidates.length,
72313
72356
  originalBytes,
72314
72357
  outputBytes,
@@ -72316,6 +72359,8 @@ async function optimizePackageAssetFiles(input) {
72316
72359
  maxImageBytes,
72317
72360
  maxTotalImageBytes,
72318
72361
  largestOutputBytes,
72362
+ ...warnings.length === 0 ? {} : { warnings },
72363
+ ...omittedImages.size === 0 ? {} : { omittedImages: [...omittedImages] },
72319
72364
  processor: "sharp",
72320
72365
  mode: input.definition?.mode ?? "webp"
72321
72366
  }
@@ -72518,6 +72563,7 @@ async function deliverPackageAssetFiles(input) {
72518
72563
  return {
72519
72564
  assets: optimization.assets,
72520
72565
  targetByOriginal: optimization.optimizedTargetByOriginal,
72566
+ ...optimization.summary.omittedImages === undefined ? {} : { omittedImages: optimization.summary.omittedImages },
72521
72567
  summary: {
72522
72568
  state: "bundled",
72523
72569
  sourceFiles: input.assets.length,
@@ -72715,7 +72761,15 @@ async function writeSelectedPackageKnowledge(input) {
72715
72761
  const results = await Promise.allSettled(projectedPages.slice(offset, offset + 8).map(async (projected) => {
72716
72762
  assertSafeRenderedPath2(projected.pageOutputPath, "knowledge path");
72717
72763
  const outputPath = join62(input.projectRoot, input.pkg.outDir, projected.pageOutputPath);
72718
- const rewritten = replaceMarkdownInlineLinkTargets(projected.content, (link) => {
72764
+ let mediaContent = projected.content;
72765
+ const omitted = new Set((delivered.omittedImages ?? []).map((path2) => packageMarkdownTarget(projected.pageOutputPath, path2)));
72766
+ for (const link of markdownReaderLinks(mediaContent).reverse()) {
72767
+ if (!omitted.has(link.target))
72768
+ continue;
72769
+ const label2 = link.label.replace(/[<>\[\]_*`]/gu, "");
72770
+ mediaContent = mediaContent.slice(0, link.start) + `[Image omitted: ${label2 || "image"}; see article sources]` + mediaContent.slice(link.end);
72771
+ }
72772
+ const rewritten = replaceMarkdownInlineLinkTargets(mediaContent, (link) => {
72719
72773
  for (const [inputPath, outputPath2] of delivered.targetByOriginal) {
72720
72774
  if (link.target === packageMarkdownTarget(projected.pageOutputPath, inputPath)) {
72721
72775
  return /^https:\/\//u.test(outputPath2) ? outputPath2 : packageMarkdownTarget(projected.pageOutputPath, outputPath2);
@@ -82592,7 +82646,7 @@ async function productionWorkflowRoute(input) {
82592
82646
  revision,
82593
82647
  reason_code: resolved.reasonCode,
82594
82648
  availability: resolved.availability,
82595
- summary: report ? `Present the report and wait. After approval, write {stage: ${stage.id}, decision: approved} to ${path2}.` : writing ? "Read the issued task directories. Coordinate them sequentially unless this caller supports independent Agents; only the coordinator submits shared state." : repair ? "Add revision tasks for rejected articles using the Review feedback; accepted production responsibilities remain unchanged." : investigate ? `Continue investigating ${stage.pending_scopes.filter((scope2) => !stage.gaps.some((gap2) => gap2.scope === scope2)).join(", ")}. Submit additional article tasks and the remaining pending_scopes; do not repeat accepted work.` : resolved.node === "resolve-production-gap" ? `Remaining investigation: ${stage.pending_scopes.join(", ") || "none"}. ${stage.gaps.map((gap2) => `${gap2.scope}: ${gap2.reason}`).join("; ")}` : "Prepare the current stage's eligible task directories.",
82649
+ summary: report ? `Present the report and wait. After approval, write {stage: ${stage.id}, decision: approved} to ${path2}.` : writing ? "Read the issued task directories. Coordinate them sequentially unless this caller supports independent Agents; only the coordinator submits shared state." : repair ? "Add revision tasks for rejected articles using the Review feedback; accepted production responsibilities remain unchanged." : investigate ? `Review pending configured scopes: ${stage.pending_scopes.filter((scope2) => !stage.gaps.some((gap2) => gap2.scope === scope2)).join(", ")}. Check their relevance to the current request and existing approved content before assigning investigation. Submit supported article tasks and remaining pending_scopes; do not infer missing articles from this list or repeat accepted work.` : resolved.node === "resolve-production-gap" ? `Source availability gaps: ${stage.gaps.map((gap2) => `${gap2.scope}: ${gap2.reason}`).join("; ")}. These failures do not establish missing knowledge or a new investigation assignment. Identify which sources the current task actually depends on; report unrelated configured-source failures separately. Preserve the configuration and follow the current resolution action.` : "Prepare the current stage's eligible task directories.",
82596
82650
  commands: report || prepare || writing || repair || investigate || gap ? [{
82597
82651
  command: command2,
82598
82652
  effect: "write",
@@ -86530,7 +86584,7 @@ class FidelityTracker {
86530
86584
  var LARK_EMPTY_SUB_PAGE_LIST_CODE = "lark.capture.sub-page-list-empty";
86531
86585
 
86532
86586
  // src/lib/larkDocxResources.ts
86533
- import { createHash as createHash24 } from "node:crypto";
86587
+ import { createHash as createHash25 } from "node:crypto";
86534
86588
  function elementName(node3) {
86535
86589
  return Object.keys(node3).find((key) => key !== ":@" && key !== "#text");
86536
86590
  }
@@ -86606,18 +86660,18 @@ function resourceIdentity(kind, attrs) {
86606
86660
  ];
86607
86661
  return candidates.find((value) => value !== undefined && value.length > 0 && !isTransientLarkMediaUrl(value));
86608
86662
  }
86609
- function registerLarkResource(ctx, blockType, kind, attrs, title) {
86663
+ function registerLarkResource(ctx, blockType, kind, attrs, title2) {
86610
86664
  const identity = resourceIdentity(kind, attrs);
86611
86665
  const href = attrs.href ?? attrs.url;
86612
86666
  const locator = identity !== undefined ? `lark:${kind}:${identity}` : href !== undefined && !isTransientLarkMediaUrl(href) ? href : undefined;
86613
- const resolvedLocator = locator ?? `lark:${kind}:unresolved:${createHash24("sha256").update(JSON.stringify(Object.fromEntries(Object.entries(attrs).sort(([left], [right]) => left.localeCompare(right))))).digest("hex").slice(0, 12)}`;
86667
+ const resolvedLocator = locator ?? `lark:${kind}:unresolved:${createHash25("sha256").update(JSON.stringify(Object.fromEntries(Object.entries(attrs).sort(([left], [right]) => left.localeCompare(right))))).digest("hex").slice(0, 12)}`;
86614
86668
  if (locator === undefined) {
86615
86669
  ctx.tracker.flag(blockType, "resource has no stable token, source id, or non-transient URL", "error");
86616
86670
  }
86617
86671
  const resource = {
86618
86672
  kind,
86619
86673
  locator: resolvedLocator,
86620
- ...title !== undefined && title.length > 0 ? { title } : {},
86674
+ ...title2 !== undefined && title2.length > 0 ? { title: title2 } : {},
86621
86675
  attributes: Object.fromEntries(Object.entries(attrs).filter(([, value]) => value.length > 0 && !isTransientLarkMediaUrl(value)))
86622
86676
  };
86623
86677
  ctx.resources.push(resource);
@@ -86635,32 +86689,32 @@ function renderLarkResource(name3, nodes, attrs, ctx) {
86635
86689
  if (name3 === "cite") {
86636
86690
  if (attrs.type === "user")
86637
86691
  return `@${attrs["user-name"] ?? (normalizeInline(textContent(nodes)) || "user")}`;
86638
- const title2 = attrs.title ?? (normalizeInline(textContent(nodes)) || "Referenced document");
86692
+ const title3 = attrs.title ?? (normalizeInline(textContent(nodes)) || "Referenced document");
86639
86693
  const docId = attrs["doc-id"] ?? attrs.token;
86640
- const resource2 = registerLarkResource(ctx, name3, "cite", attrs, title2);
86694
+ const resource2 = registerLarkResource(ctx, name3, "cite", attrs, title3);
86641
86695
  if (docId === undefined) {
86642
86696
  ctx.tracker.flag(name3, "cite has no doc-id or token", "error");
86643
- return `${escapeMarkdownLabel(title2)} <!-- ${resource2.locator} -->`;
86697
+ return `${escapeMarkdownLabel(title3)} <!-- ${resource2.locator} -->`;
86644
86698
  }
86645
- return `[${escapeMarkdownLabel(title2)}](${stableDocumentUrl(ctx.sourceUrl, attrs["file-type"], docId)}) <!-- ${resource2.locator} -->`;
86699
+ return `[${escapeMarkdownLabel(title3)}](${stableDocumentUrl(ctx.sourceUrl, attrs["file-type"], docId)}) <!-- ${resource2.locator} -->`;
86646
86700
  }
86647
86701
  if (name3 === "img" || name3 === "image") {
86648
- const title2 = attrs.alt ?? attrs.name ?? "Image";
86649
- const resource2 = registerLarkResource(ctx, name3, "image", attrs, title2);
86702
+ const title3 = attrs.alt ?? attrs.name ?? "Image";
86703
+ const resource2 = registerLarkResource(ctx, name3, "image", attrs, title3);
86650
86704
  return `
86651
86705
 
86652
- > Image: ${title2} (${resource2.locator})
86706
+ > Image: ${title3} (${resource2.locator})
86653
86707
 
86654
86708
  `;
86655
86709
  }
86656
86710
  if (name3 === "source" || name3 === "file" || name3 === "attachment") {
86657
86711
  const isVideo = attrs.mime?.startsWith("video/") === true || attrs.type === "video";
86658
86712
  const kind2 = isVideo ? "video" : "file";
86659
- const title2 = attrs.name ?? (isVideo ? "Video" : "File");
86660
- const resource2 = registerLarkResource(ctx, name3, kind2, attrs, title2);
86713
+ const title3 = attrs.name ?? (isVideo ? "Video" : "File");
86714
+ const resource2 = registerLarkResource(ctx, name3, kind2, attrs, title3);
86661
86715
  return `
86662
86716
 
86663
- > ${isVideo ? "Video" : "File"}: ${title2} (${resource2.locator})
86717
+ > ${isVideo ? "Video" : "File"}: ${title3} (${resource2.locator})
86664
86718
 
86665
86719
  `;
86666
86720
  }
@@ -86680,7 +86734,7 @@ ${safeFence(source2, attrs.type === "mermaid" ? "mermaid" : "text")}
86680
86734
  }
86681
86735
  if (name3 === "diagram") {
86682
86736
  const source2 = textContent(nodes).trim();
86683
- const resourceAttrs = source2.length > 0 ? { ...attrs, "content-hash": createHash24("sha256").update(source2, "utf8").digest("hex") } : attrs;
86737
+ const resourceAttrs = source2.length > 0 ? { ...attrs, "content-hash": createHash25("sha256").update(source2, "utf8").digest("hex") } : attrs;
86684
86738
  const resource2 = registerLarkResource(ctx, name3, "diagram", resourceAttrs, attrs.title ?? "Diagram");
86685
86739
  if (source2.length > 0) {
86686
86740
  resource2.inline_content = true;
@@ -86699,92 +86753,92 @@ ${safeFence(source2, attrs.type ?? "text")}
86699
86753
  `;
86700
86754
  }
86701
86755
  if (name3 === "chat_card") {
86702
- const title2 = attrs.name ?? "Chat";
86703
- const resource2 = registerLarkResource(ctx, name3, "chat", attrs, title2);
86756
+ const title3 = attrs.name ?? "Chat";
86757
+ const resource2 = registerLarkResource(ctx, name3, "chat", attrs, title3);
86704
86758
  return `
86705
86759
 
86706
- > Chat: ${title2} (${resource2.locator})
86760
+ > Chat: ${title3} (${resource2.locator})
86707
86761
 
86708
86762
  `;
86709
86763
  }
86710
86764
  if (name3 === "readonly-block") {
86711
- const title2 = attrs.type ?? "Read-only embedded block";
86765
+ const title3 = attrs.type ?? "Read-only embedded block";
86712
86766
  const kind2 = attrs.type === "diagram" ? "diagram" : "embed";
86713
- const resource2 = registerLarkResource(ctx, name3, kind2, attrs, title2);
86767
+ const resource2 = registerLarkResource(ctx, name3, kind2, attrs, title3);
86714
86768
  return kind2 === "diagram" ? `
86715
86769
 
86716
86770
  > Diagram: ${resource2.locator}
86717
86771
 
86718
86772
  ` : `
86719
86773
 
86720
- > Embedded block: ${title2} (${resource2.locator})
86774
+ > Embedded block: ${title3} (${resource2.locator})
86721
86775
 
86722
86776
  `;
86723
86777
  }
86724
86778
  const kind = name3 === "sheet" ? "sheet" : "base";
86725
- const title = attrs.title ?? (kind === "sheet" ? "Embedded Sheet" : "Embedded Base");
86726
- const resource = registerLarkResource(ctx, name3, kind, attrs, title);
86779
+ const title2 = attrs.title ?? (kind === "sheet" ? "Embedded Sheet" : "Embedded Base");
86780
+ const resource = registerLarkResource(ctx, name3, kind, attrs, title2);
86727
86781
  const details = [attrs["table-id"], attrs["sheet-id"], attrs["view-id"]].filter(Boolean).join(" / ");
86728
86782
  return `
86729
86783
 
86730
- > ${title}${details.length > 0 ? ` — ${details}` : ""} (${resource.locator})
86784
+ > ${title2}${details.length > 0 ? ` — ${details}` : ""} (${resource.locator})
86731
86785
 
86732
86786
  `;
86733
86787
  }
86734
86788
  function renderLarkSubPage(nodes, attrs, ctx) {
86735
- const title = attrs.title ?? (normalizeInline(textContent(nodes)) || "Untitled subpage");
86789
+ const title2 = attrs.title ?? (normalizeInline(textContent(nodes)) || "Untitled subpage");
86736
86790
  const docId = attrs["doc-id"] ?? attrs.token;
86737
- const resource = registerLarkResource(ctx, "sub-page", "document", attrs, title);
86791
+ const resource = registerLarkResource(ctx, "sub-page", "document", attrs, title2);
86738
86792
  if (docId === undefined) {
86739
86793
  ctx.tracker.flag("sub-page", "sub-page has no doc-id or token", "error");
86740
- return `${escapeMarkdownLabel(title)} <!-- ${resource.locator} -->`;
86794
+ return `${escapeMarkdownLabel(title2)} <!-- ${resource.locator} -->`;
86741
86795
  }
86742
- return `[${escapeMarkdownLabel(title)}](${stableDocumentUrl(ctx.sourceUrl, attrs["file-type"], docId)}) <!-- ${resource.locator} -->`;
86796
+ return `[${escapeMarkdownLabel(title2)}](${stableDocumentUrl(ctx.sourceUrl, attrs["file-type"], docId)}) <!-- ${resource.locator} -->`;
86743
86797
  }
86744
86798
  function renderLarkBookmark(nodes, attrs, ctx) {
86745
86799
  const href = attrs.href ?? attrs.url;
86746
- const title = attrs.name ?? attrs.title ?? (normalizeInline(textContent(nodes)) || href) ?? "Bookmark";
86747
- const resource = registerLarkResource(ctx, "bookmark", "bookmark", attrs, title);
86800
+ const title2 = attrs.name ?? attrs.title ?? (normalizeInline(textContent(nodes)) || href) ?? "Bookmark";
86801
+ const resource = registerLarkResource(ctx, "bookmark", "bookmark", attrs, title2);
86748
86802
  if (href === undefined || isTransientLarkMediaUrl(href)) {
86749
86803
  ctx.tracker.flag("bookmark", "bookmark has no stable non-transient URL", "error");
86750
86804
  return `
86751
86805
 
86752
- > Bookmark: ${escapeMarkdownLabel(title)} <!-- ${resource.locator} -->
86806
+ > Bookmark: ${escapeMarkdownLabel(title2)} <!-- ${resource.locator} -->
86753
86807
 
86754
86808
  `;
86755
86809
  }
86756
86810
  return `
86757
86811
 
86758
- > Bookmark: [${escapeMarkdownLabel(title)}](${href}) <!-- ${resource.locator} -->
86812
+ > Bookmark: [${escapeMarkdownLabel(title2)}](${href}) <!-- ${resource.locator} -->
86759
86813
 
86760
86814
  `;
86761
86815
  }
86762
86816
  function renderLarkSyncedReference(attrs, ctx) {
86763
86817
  const sourceToken = attrs["src-token"];
86764
86818
  const sourceBlockId = attrs["src-block-id"];
86765
- const title = attrs.title ?? attrs.name ?? "Synced reference";
86766
- const resource = registerLarkResource(ctx, "synced_reference", "synced-reference", attrs, title);
86819
+ const title2 = attrs.title ?? attrs.name ?? "Synced reference";
86820
+ const resource = registerLarkResource(ctx, "synced_reference", "synced-reference", attrs, title2);
86767
86821
  if (sourceToken === undefined || sourceBlockId === undefined) {
86768
86822
  ctx.tracker.flag("synced_reference", "synced_reference requires both src-token and src-block-id", "error");
86769
86823
  return `
86770
86824
 
86771
- > ${escapeMarkdownLabel(title)} <!-- ${resource.locator} -->
86825
+ > ${escapeMarkdownLabel(title2)} <!-- ${resource.locator} -->
86772
86826
 
86773
86827
  `;
86774
86828
  }
86775
86829
  const target = `${stableDocumentUrl(ctx.sourceUrl, "docx", sourceToken)}#${encodeURIComponent(sourceBlockId)}`;
86776
86830
  return `
86777
86831
 
86778
- > [${escapeMarkdownLabel(title)}](${target}) <!-- ${resource.locator} -->
86832
+ > [${escapeMarkdownLabel(title2)}](${target}) <!-- ${resource.locator} -->
86779
86833
 
86780
86834
  `;
86781
86835
  }
86782
86836
  var init_larkDocxResources = () => {};
86783
86837
 
86784
86838
  // src/lib/larkDocxXml.ts
86785
- import { createHash as createHash25 } from "node:crypto";
86839
+ import { createHash as createHash26 } from "node:crypto";
86786
86840
  function sha256(value) {
86787
- return `sha256:${createHash25("sha256").update(value, "utf8").digest("hex")}`;
86841
+ return `sha256:${createHash26("sha256").update(value, "utf8").digest("hex")}`;
86788
86842
  }
86789
86843
  function elementName2(node3) {
86790
86844
  return Object.keys(node3).find((key) => key !== ":@" && key !== "#text");
@@ -86835,9 +86889,9 @@ function renderList(nodes, ctx, ordered) {
86835
86889
  const name3 = elementName2(item);
86836
86890
  ctx.tracker.discover(name3);
86837
86891
  ctx.tracker.convert(name3);
86838
- const body = normalizeMarkdown(renderChildren(elementChildren2(item, name3), { ...ctx, mode: "inline" }));
86892
+ const body2 = normalizeMarkdown(renderChildren(elementChildren2(item, name3), { ...ctx, mode: "inline" }));
86839
86893
  const marker = ordered ? `${index2 + 1}.` : "-";
86840
- return body.split(`
86894
+ return body2.split(`
86841
86895
  `).map((line, lineIndex) => lineIndex === 0 ? `${marker} ${line}` : ` ${line}`).join(`
86842
86896
  `);
86843
86897
  }).join(`
@@ -86913,15 +86967,15 @@ function renderChecklistItem(blockType, nodes, attrs, ctx) {
86913
86967
  const checked = attrs.checked;
86914
86968
  const state = done ?? checked;
86915
86969
  const stateIsValid = (state === "true" || state === "false") && (done === undefined || checked === undefined || done === checked);
86916
- const body = normalizeInline2(renderChildren(nodes, { ...ctx, mode: "inline" }));
86970
+ const body2 = normalizeInline2(renderChildren(nodes, { ...ctx, mode: "inline" }));
86917
86971
  if (!stateIsValid) {
86918
86972
  ctx.tracker.flag(blockType, `${blockType} requires one unambiguous boolean done or checked attribute`, "warning", "lark.capture.checkbox-state-invalid", "projection");
86919
86973
  return `
86920
- - [?] ${body}
86974
+ - [?] ${body2}
86921
86975
  `;
86922
86976
  }
86923
86977
  return `
86924
- - [${state === "true" ? "x" : " "}] ${body}
86978
+ - [${state === "true" ? "x" : " "}] ${body2}
86925
86979
  `;
86926
86980
  }
86927
86981
  function meaningfulAttributes(attrs, excluded2) {
@@ -86931,8 +86985,8 @@ function auditableAttributes(attrs) {
86931
86985
  return Object.entries(attrs).filter(([, value]) => value.length > 0).map(([key, value]) => [key, isTransientLarkMediaUrl(value) ? "[redacted-transient-url]" : value]).sort(([left], [right]) => left.localeCompare(right));
86932
86986
  }
86933
86987
  function renderPollOption(blockType, nodes, attrs, ctx) {
86934
- const body = normalizeInline2(renderChildren(nodes, { ...ctx, mode: "inline" }));
86935
- const label2 = body || attrs.name || attrs.title || attrs.label || attrs.value;
86988
+ const body2 = normalizeInline2(renderChildren(nodes, { ...ctx, mode: "inline" }));
86989
+ const label2 = body2 || attrs.name || attrs.title || attrs.label || attrs.value;
86936
86990
  const details = meaningfulAttributes(attrs, new Set(["name", "title", "label", "value"])).map(([key, value]) => `${key}=${value}`);
86937
86991
  if (label2 === undefined && details.length === 0) {
86938
86992
  ctx.tracker.flag(blockType, `${blockType} has no visible label or metadata`, "warning", "lark.capture.poll-option-empty", "projection");
@@ -86943,10 +86997,10 @@ function renderPollOption(blockType, nodes, attrs, ctx) {
86943
86997
  `;
86944
86998
  }
86945
86999
  function renderPoll(nodes, attrs, ctx) {
86946
- const title = attrs.name ?? attrs.title ?? "Untitled poll";
86947
- const resource = registerLarkResource(ctx, "poll", "poll", attrs, title);
87000
+ const title2 = attrs.name ?? attrs.title ?? "Untitled poll";
87001
+ const resource = registerLarkResource(ctx, "poll", "poll", attrs, title2);
86948
87002
  const href = attrs.href ?? attrs.url;
86949
- const label2 = href !== undefined && !isTransientLarkMediaUrl(href) ? `[${escapeMarkdownLabel2(title)}](${href})` : escapeMarkdownLabel2(title);
87003
+ const label2 = href !== undefined && !isTransientLarkMediaUrl(href) ? `[${escapeMarkdownLabel2(title2)}](${href})` : escapeMarkdownLabel2(title2);
86950
87004
  const details = meaningfulAttributes(attrs, new Set(["name", "title", "href", "url"])).map(([key, value]) => `${key}=${value}`);
86951
87005
  const children = normalizeMarkdown(renderChildren(nodes, { ...ctx, mode: "block" }));
86952
87006
  resource.inline_content = children.length > 0;
@@ -86963,15 +87017,15 @@ ${lines.join(`
86963
87017
  `;
86964
87018
  }
86965
87019
  function renderUnknown(name3, nodes, attrs, ctx) {
86966
- const body = normalizeMarkdown(renderChildren(nodes, ctx));
87020
+ const body2 = normalizeMarkdown(renderChildren(nodes, ctx));
86967
87021
  const exportedAttrs = auditableAttributes(attrs);
86968
- if (body.length === 0 && exportedAttrs.length === 0) {
87022
+ if (body2.length === 0 && exportedAttrs.length === 0) {
86969
87023
  ctx.tracker.skip(name3, "unknown empty block omitted", "warning");
86970
87024
  return "";
86971
87025
  }
86972
87026
  ctx.tracker.convert(name3);
86973
87027
  ctx.tracker.flag(name3, "block was preserved through the generic non-interactive projection; inspect source.xml for the original structure", "warning", "lark.capture.generic-projection", "projection");
86974
- const digest6 = createHash25("sha256").update(JSON.stringify({ name: name3, attributes: exportedAttrs, text: normalizeInline2(textContent2(nodes)) }), "utf8").digest("hex").slice(0, 12);
87028
+ const digest6 = createHash26("sha256").update(JSON.stringify({ name: name3, attributes: exportedAttrs, text: normalizeInline2(textContent2(nodes)) }), "utf8").digest("hex").slice(0, 12);
86975
87029
  const locator = `lark:block:${name3}:${digest6}`;
86976
87030
  ctx.resources.push({
86977
87031
  kind: "embed",
@@ -86982,7 +87036,7 @@ function renderUnknown(name3, nodes, attrs, ctx) {
86982
87036
  const lines = [
86983
87037
  `> Lark block (generic projection): \`${name3}\` <!-- ${locator} -->`,
86984
87038
  ...exportedAttrs.length > 0 ? [`> Exported attributes: ${JSON.stringify(Object.fromEntries(exportedAttrs))}`] : [],
86985
- ...body.length > 0 ? [body] : []
87039
+ ...body2.length > 0 ? [body2] : []
86986
87040
  ];
86987
87041
  return `
86988
87042
 
@@ -87031,10 +87085,10 @@ ${"#".repeat(Math.min(Math.max(level, 1), 6))} ${normalizeInline2(renderChildren
87031
87085
  }
87032
87086
  if (["p", "paragraph", "div", "section"].includes(name3)) {
87033
87087
  ctx.tracker.convert(name3);
87034
- const body = renderChildren(nodes, { ...ctx, mode: "inline" });
87035
- return ctx.mode === "inline" ? body : `
87088
+ const body2 = renderChildren(nodes, { ...ctx, mode: "inline" });
87089
+ return ctx.mode === "inline" ? body2 : `
87036
87090
 
87037
- ${body}
87091
+ ${body2}
87038
87092
 
87039
87093
  `;
87040
87094
  }
@@ -87074,8 +87128,8 @@ ${body}
87074
87128
  }
87075
87129
  if (name3 === "code") {
87076
87130
  ctx.tracker.convert(name3);
87077
- const body = renderChildren(nodes, { ...ctx, mode: "code" });
87078
- return ctx.mode === "code" ? body : `\`${body.replace(/`/gu, "\\`")}\``;
87131
+ const body2 = renderChildren(nodes, { ...ctx, mode: "code" });
87132
+ return ctx.mode === "code" ? body2 : `\`${body2.replace(/`/gu, "\\`")}\``;
87079
87133
  }
87080
87134
  if (name3 === "pre") {
87081
87135
  ctx.tracker.convert(name3);
@@ -87127,10 +87181,10 @@ ${renderTable(nodes, ctx)}
87127
87181
  if (name3 === "callout" || name3 === "blockquote" || name3 === "quote") {
87128
87182
  ctx.tracker.convert(name3);
87129
87183
  const prefix = attrs.emoji === undefined ? "" : `${attrs.emoji} `;
87130
- const body = normalizeMarkdown(renderChildren(nodes, { ...ctx, mode: "block" }));
87184
+ const body2 = normalizeMarkdown(renderChildren(nodes, { ...ctx, mode: "block" }));
87131
87185
  return `
87132
87186
 
87133
- ${body.split(`
87187
+ ${body2.split(`
87134
87188
  `).map((line, index2) => `> ${index2 === 0 ? prefix : ""}${line}`).join(`
87135
87189
  `)}
87136
87190
 
@@ -87186,10 +87240,10 @@ function projectLarkDocxXml(input) {
87186
87240
  mode: "block"
87187
87241
  }));
87188
87242
  const titleNode = rootChildren.find((node3) => elementName2(node3) === "title");
87189
- const title = titleNode === undefined ? undefined : normalizeInline2(textContent2(elementChildren2(titleNode, "title")));
87243
+ const title2 = titleNode === undefined ? undefined : normalizeInline2(textContent2(elementChildren2(titleNode, "title")));
87190
87244
  return {
87191
87245
  markdown,
87192
- ...title !== undefined && title.length > 0 ? { title } : {},
87246
+ ...title2 !== undefined && title2.length > 0 ? { title: title2 } : {},
87193
87247
  auditXml: sanitizeAuditXml(input.xml),
87194
87248
  rawContentHash,
87195
87249
  resources,
@@ -87262,6 +87316,32 @@ var init_larkDocxXml = __esm(() => {
87262
87316
  ]);
87263
87317
  });
87264
87318
 
87319
+ // src/lib/larkImagePolicy.ts
87320
+ function omitLarkImage(resource, policy, items, replacements, mediaType, failure2) {
87321
+ if (resource.kind !== "image" && !mediaType?.startsWith("image/"))
87322
+ return false;
87323
+ const gif = mediaType === "image/gif" || /\.gif$/iu.test(resource.title ?? "") || Object.entries(resource.attributes).some(([key, value]) => ["mime_type", "content_type"].includes(key) && value === "image/gif");
87324
+ if (!failure2 && policy.images !== "reference-only" && !(gif && policy.gifs === "reference-only"))
87325
+ return false;
87326
+ const title2 = (resource.title ?? "image").replace(/[\r\n<>]/gu, " ");
87327
+ replacements.set(resource.locator, `> Image omitted: ${title2}. ${failure2 ? "Resource size limit exceeded." : "Excluded by selected policy."} See the source document. <!-- ${resource.locator} -->`);
87328
+ items.push({
87329
+ kind: resource.kind,
87330
+ locator: resource.locator,
87331
+ status: "reference-only",
87332
+ required: false,
87333
+ asset_paths: [],
87334
+ reason_code: failure2 ? "image-budget-exceeded" : "image-excluded-by-policy",
87335
+ reason: failure2 ?? "Image bytes were not retained; the selected image policy preserves a placeholder and source reference"
87336
+ });
87337
+ return true;
87338
+ }
87339
+ var LarkResourceBudgetError;
87340
+ var init_larkImagePolicy = __esm(() => {
87341
+ LarkResourceBudgetError = class LarkResourceBudgetError extends Error {
87342
+ };
87343
+ });
87344
+
87265
87345
  // src/lib/larkResourceCommand.ts
87266
87346
  function stableJson(value) {
87267
87347
  if (value === undefined)
@@ -87331,9 +87411,9 @@ var init_larkResourceCommand = __esm(() => {
87331
87411
  });
87332
87412
 
87333
87413
  // src/lib/larkResourceMaterialization.ts
87334
- import { createHash as createHash26 } from "node:crypto";
87335
- import { mkdtemp as mkdtemp4, readFile as readFile69, readdir as readdir20, rm as rm19 } from "node:fs/promises";
87336
- import { extname as extname13, join as join89 } from "node:path";
87414
+ import { createHash as createHash27 } from "node:crypto";
87415
+ import { mkdtemp as mkdtemp4, readFile as readFile71, readdir as readdir21, rm as rm19 } from "node:fs/promises";
87416
+ import { extname as extname13, join as join91 } from "node:path";
87337
87417
  import { tmpdir } from "node:os";
87338
87418
  function countByKind(items, status) {
87339
87419
  const counts2 = new Map;
@@ -87345,7 +87425,7 @@ function countByKind(items, status) {
87345
87425
  return Object.fromEntries([...counts2].sort(([left], [right]) => left.localeCompare(right)));
87346
87426
  }
87347
87427
  function resourceDigest(resource) {
87348
- return createHash26("sha256").update(`${resource.kind}\x00${resource.locator}`, "utf8").digest("hex").slice(0, 20);
87428
+ return createHash27("sha256").update(`${resource.kind}\x00${resource.locator}`, "utf8").digest("hex").slice(0, 20);
87349
87429
  }
87350
87430
  function safeLabel(value, fallback) {
87351
87431
  const normalized = (value ?? fallback).replace(/[\r\n]+/gu, " ").trim();
@@ -87438,10 +87518,10 @@ function findBooleanField(value, name3) {
87438
87518
  }
87439
87519
  async function downloadedFile(input) {
87440
87520
  if (input.localPath !== undefined) {
87441
- const bytes = await readFile69(input.localPath);
87521
+ const bytes = await readFile71(input.localPath);
87442
87522
  return { path: input.localPath, bytes, mediaType: mediaTypeFor(input.localPath, bytes) };
87443
87523
  }
87444
- const tempRoot = await mkdtemp4(join89(tmpdir(), "context-lark-resource-"));
87524
+ const tempRoot = await mkdtemp4(join91(tmpdir(), "context-lark-resource-"));
87445
87525
  try {
87446
87526
  await runLarkResourceCommand(input.runner, [
87447
87527
  "docs",
@@ -87458,11 +87538,11 @@ async function downloadedFile(input) {
87458
87538
  "--format",
87459
87539
  "json"
87460
87540
  ], { cwd: tempRoot });
87461
- const entries2 = (await readdir20(tempRoot, { withFileTypes: true })).filter((entry) => entry.isFile());
87541
+ const entries2 = (await readdir21(tempRoot, { withFileTypes: true })).filter((entry) => entry.isFile());
87462
87542
  if (entries2.length !== 1)
87463
87543
  throw new Error(`media download produced ${entries2.length} files, expected exactly one`);
87464
87544
  const path3 = entries2[0]?.name ?? "resource.bin";
87465
- const bytes = await readFile69(join89(tempRoot, path3));
87545
+ const bytes = await readFile71(join91(tempRoot, path3));
87466
87546
  return { path: path3, bytes, mediaType: mediaTypeFor(path3, bytes) };
87467
87547
  } finally {
87468
87548
  await rm19(tempRoot, { recursive: true, force: true });
@@ -87553,9 +87633,9 @@ async function sheetMaterialization(resource, runner2, identity) {
87553
87633
  const sheetId = resource.attributes["sheet-id"];
87554
87634
  if (token === undefined || sheetId === undefined)
87555
87635
  throw new Error("embedded Sheet requires token and sheet-id");
87556
- const tempRoot = await mkdtemp4(join89(tmpdir(), "context-lark-sheet-"));
87636
+ const tempRoot = await mkdtemp4(join91(tmpdir(), "context-lark-sheet-"));
87557
87637
  try {
87558
- const outputPath = join89(tempRoot, "sheet.json");
87638
+ const outputPath = join91(tempRoot, "sheet.json");
87559
87639
  const stdout = await runLarkResourceCommand(runner2, [
87560
87640
  "sheets",
87561
87641
  "+csv-get",
@@ -87575,13 +87655,13 @@ async function sheetMaterialization(resource, runner2, identity) {
87575
87655
  if (findBooleanField(receipt2, "truncated") === true || findBooleanField(receipt2, "complete") === false) {
87576
87656
  throw new Error("embedded Sheet read was truncated");
87577
87657
  }
87578
- const payload = JSON.parse(await readFile69(outputPath, "utf8"));
87658
+ const payload = JSON.parse(await readFile71(outputPath, "utf8"));
87579
87659
  const csv = findStringField(payload, new Set(["annotated_csv", "csv", "content", "text"]));
87580
87660
  if (csv === undefined)
87581
87661
  throw new Error("embedded Sheet response has no CSV payload");
87582
87662
  const digest6 = resourceDigest(resource);
87583
87663
  const path3 = `materialized/sheet/${digest6}.csv`;
87584
- const title = safeLabel(resource.title, "Embedded Sheet");
87664
+ const title2 = safeLabel(resource.title, "Embedded Sheet");
87585
87665
  return {
87586
87666
  asset: {
87587
87667
  path: path3,
@@ -87590,7 +87670,7 @@ async function sheetMaterialization(resource, runner2, identity) {
87590
87670
  role: "evidence",
87591
87671
  source: { kind: resource.kind, locator: resource.locator }
87592
87672
  },
87593
- replacement: `#### ${title}
87673
+ replacement: `#### ${title2}
87594
87674
 
87595
87675
  ${markdownTable2(parseCsv(csv))}
87596
87676
 
@@ -87690,7 +87770,7 @@ async function baseMaterialization(resource, runner2, identity) {
87690
87770
  `;
87691
87771
  const digest6 = resourceDigest(resource);
87692
87772
  const path3 = `materialized/base/${digest6}.json`;
87693
- const title = safeLabel(resource.title, "Embedded Base");
87773
+ const title2 = safeLabel(resource.title, "Embedded Base");
87694
87774
  return {
87695
87775
  asset: {
87696
87776
  path: path3,
@@ -87699,7 +87779,7 @@ async function baseMaterialization(resource, runner2, identity) {
87699
87779
  role: "evidence",
87700
87780
  source: { kind: resource.kind, locator: resource.locator }
87701
87781
  },
87702
- replacement: `#### ${title}
87782
+ replacement: `#### ${title2}
87703
87783
 
87704
87784
  ${markdownTable2(baseRows(records, fieldOrder))}
87705
87785
 
@@ -87711,7 +87791,7 @@ async function whiteboardMaterialization(resource, runner2, identity) {
87711
87791
  if (token === undefined)
87712
87792
  throw new Error(`${resource.kind} has no whiteboard token`);
87713
87793
  const preview = await downloadedFile({ runner: runner2, identity, token, type: "whiteboard" });
87714
- const tempRoot = await mkdtemp4(join89(tmpdir(), "context-lark-whiteboard-"));
87794
+ const tempRoot = await mkdtemp4(join91(tmpdir(), "context-lark-whiteboard-"));
87715
87795
  let rawPayload;
87716
87796
  try {
87717
87797
  await runLarkResourceCommand(runner2, [
@@ -87729,14 +87809,14 @@ async function whiteboardMaterialization(resource, runner2, identity) {
87729
87809
  "--format",
87730
87810
  "json"
87731
87811
  ], { cwd: tempRoot });
87732
- rawPayload = JSON.parse(await readFile69(join89(tempRoot, "raw.json"), "utf8"));
87812
+ rawPayload = JSON.parse(await readFile71(join91(tempRoot, "raw.json"), "utf8"));
87733
87813
  } finally {
87734
87814
  await rm19(tempRoot, { recursive: true, force: true });
87735
87815
  }
87736
87816
  const digest6 = resourceDigest(resource);
87737
87817
  const previewPath = `materialized/${resource.kind}/${digest6}${extensionFor2(preview.mediaType, preview.path)}`;
87738
87818
  const rawPath = `materialized/${resource.kind}/${digest6}.json`;
87739
- const title = markdownLabel(safeLabel(resource.title, resource.kind === "diagram" ? "Diagram" : "Whiteboard"));
87819
+ const title2 = markdownLabel(safeLabel(resource.title, resource.kind === "diagram" ? "Diagram" : "Whiteboard"));
87740
87820
  return {
87741
87821
  assets: [
87742
87822
  {
@@ -87755,20 +87835,20 @@ async function whiteboardMaterialization(resource, runner2, identity) {
87755
87835
  source: { kind: resource.kind, locator: resource.locator }
87756
87836
  }
87757
87837
  ],
87758
- replacement: `![${title}](${sourceAssetTarget(previewPath)})
87838
+ replacement: `![${title2}](${sourceAssetTarget(previewPath)})
87759
87839
 
87760
87840
  [Raw snapshot](${sourceAssetTarget(rawPath)}) <!-- ${resource.locator} -->`
87761
87841
  };
87762
87842
  }
87763
87843
  function placeholderFor(resource) {
87764
- const title = safeLabel(resource.title, resource.kind);
87844
+ const title2 = safeLabel(resource.title, resource.kind);
87765
87845
  switch (resource.kind) {
87766
87846
  case "image":
87767
- return `> Image: ${title} (${resource.locator})`;
87847
+ return `> Image: ${title2} (${resource.locator})`;
87768
87848
  case "video":
87769
- return `> Video: ${title} (${resource.locator})`;
87849
+ return `> Video: ${title2} (${resource.locator})`;
87770
87850
  case "file":
87771
- return `> File: ${title} (${resource.locator})`;
87851
+ return `> File: ${title2} (${resource.locator})`;
87772
87852
  case "whiteboard":
87773
87853
  return `> Whiteboard: ${resource.locator}`;
87774
87854
  case "diagram":
@@ -87790,7 +87870,7 @@ function referenceOnlyReason(resource) {
87790
87870
  }
87791
87871
  function materializationReport(items) {
87792
87872
  const hasRequiredFailure = items.some((item) => item.status === "failed" && item.required && !isNonBlockingDocumentResourceFailureReasonCode(item.reason_code));
87793
- const hasOptionalFailure = items.some((item) => item.status === "failed") || items.some((item) => item.status === "reference-only" && item.kind === "poll" && item.reason?.includes("absent") === true);
87873
+ const hasOptionalFailure = items.some((item) => item.status === "failed" || item.reason_code === "image-budget-exceeded") || items.some((item) => item.status === "reference-only" && item.kind === "poll" && item.reason?.includes("absent") === true);
87794
87874
  return {
87795
87875
  status: hasRequiredFailure ? "error" : hasOptionalFailure ? "warning" : "complete",
87796
87876
  discovered: countByKind(items),
@@ -87810,16 +87890,16 @@ function resourceFailureReasonCode(resource, error) {
87810
87890
  return /\b2890003\b/u.test(message) ? DOCUMENT_RESOURCE_SOURCE_MISSING_REASON_CODE : undefined;
87811
87891
  }
87812
87892
  function unavailableReplacement(resource, reasonCode) {
87813
- const title = markdownLabel(safeLabel(resource.title, resource.kind));
87893
+ const title2 = markdownLabel(safeLabel(resource.title, resource.kind));
87814
87894
  const reason = reasonCode === DOCUMENT_RESOURCE_PERMISSION_DENIED_REASON_CODE ? "export permission denied" : "source no longer exists";
87815
- return `> Resource unavailable: ${title} (${resource.kind}; ${reason}). <!-- ${resource.locator} -->`;
87895
+ return `> Resource unavailable: ${title2} (${resource.kind}; ${reason}). <!-- ${resource.locator} -->`;
87816
87896
  }
87817
87897
  function assertBudget(asset, policy, total) {
87818
87898
  if (asset.bytes.byteLength > policy.maxBytesPerResource) {
87819
- throw new Error(`resource is ${asset.bytes.byteLength} bytes, above maxBytesPerResource=${policy.maxBytesPerResource}`);
87899
+ throw new LarkResourceBudgetError(`resource is ${asset.bytes.byteLength} bytes, above maxBytesPerResource=${policy.maxBytesPerResource}`);
87820
87900
  }
87821
87901
  if (total + asset.bytes.byteLength > policy.maxTotalBytes) {
87822
- throw new Error(`materialized resources exceed maxTotalBytes=${policy.maxTotalBytes}`);
87902
+ throw new LarkResourceBudgetError(`materialized resources exceed maxTotalBytes=${policy.maxTotalBytes}`);
87823
87903
  }
87824
87904
  }
87825
87905
  async function materializeLarkResources(input) {
@@ -87835,6 +87915,8 @@ async function materializeLarkResources(input) {
87835
87915
  return;
87836
87916
  seen.add(key);
87837
87917
  const required = REQUIRED_KINDS.has(resource.kind);
87918
+ if (omitLarkImage(resource, input.policy, items, replacements))
87919
+ return;
87838
87920
  try {
87839
87921
  if (resource.kind === "diagram" && resource.inline_content === true) {
87840
87922
  replacements.set(resource.locator, "");
@@ -87921,6 +88003,8 @@ async function materializeLarkResources(input) {
87921
88003
  type: "media",
87922
88004
  ...input.mediaFiles?.[token] === undefined ? {} : { localPath: input.mediaFiles[token] }
87923
88005
  });
88006
+ if (omitLarkImage(resource, input.policy, items, replacements, downloaded.mediaType))
88007
+ return;
87924
88008
  const digest6 = resourceDigest(resource);
87925
88009
  const extension2 = extensionFor2(downloaded.mediaType, downloaded.path);
87926
88010
  const asset = {
@@ -87933,12 +88017,14 @@ async function materializeLarkResources(input) {
87933
88017
  assertBudget(asset, input.policy, totalBytes);
87934
88018
  totalBytes += asset.bytes.byteLength;
87935
88019
  assets.push(asset);
87936
- const title = markdownLabel(safeLabel(resource.title, resource.kind));
88020
+ const title2 = markdownLabel(safeLabel(resource.title, resource.kind));
87937
88021
  const target = sourceAssetTarget(asset.path);
87938
- const replacement = downloaded.mediaType.startsWith("image/") ? `![${title}](${target}) <!-- ${resource.locator} -->` : `[${title}](${target}) <!-- ${resource.locator} -->`;
88022
+ const replacement = downloaded.mediaType.startsWith("image/") ? `![${title2}](${target}) <!-- ${resource.locator} -->` : `[${title2}](${target}) <!-- ${resource.locator} -->`;
87939
88023
  replacements.set(resource.locator, replacement);
87940
88024
  items.push({ kind: resource.kind, locator: resource.locator, status: "materialized", required, asset_paths: [asset.path] });
87941
88025
  } catch (error) {
88026
+ if (error instanceof LarkResourceBudgetError && omitLarkImage(resource, input.policy, items, replacements, undefined, error.message))
88027
+ return;
87942
88028
  const reasonCode = resourceFailureReasonCode(resource, error);
87943
88029
  if (isNonBlockingDocumentResourceFailureReasonCode(reasonCode)) {
87944
88030
  replacements.set(resource.locator, unavailableReplacement(resource, reasonCode));
@@ -87983,6 +88069,7 @@ function applyLarkResourceReplacements(markdown, resources, replacements) {
87983
88069
  }
87984
88070
  var REQUIRED_KINDS, MEDIA_TYPES_BY_EXTENSION;
87985
88071
  var init_larkResourceMaterialization = __esm(() => {
88072
+ init_larkImagePolicy();
87986
88073
  init_src3();
87987
88074
  init_larkResourceCommand();
87988
88075
  REQUIRED_KINDS = new Set([
@@ -88155,9 +88242,9 @@ function payloadShapeSummary(payload) {
88155
88242
  }
88156
88243
  function extractDocsFetchContent(payload, requestedFormat) {
88157
88244
  const document4 = payload.document && typeof payload.document === "object" ? payload.document : undefined;
88158
- const title = stringValue(payload.title) ?? stringValue(document4?.title);
88245
+ const title2 = stringValue(payload.title) ?? stringValue(document4?.title);
88159
88246
  const markdown = stringValue(payload.markdown) ?? stringValue(document4?.markdown);
88160
- const withTitle = (result) => title === undefined ? result : { ...result, title };
88247
+ const withTitle = (result) => title2 === undefined ? result : { ...result, title: title2 };
88161
88248
  if (markdown !== undefined) {
88162
88249
  if (requestedFormat === "xml") {
88163
88250
  throw new LarkCliError(`${LARK_BIN} docs +fetch returned Markdown despite --doc-format xml; capture stopped because the response cannot provide auditable rich-block fidelity. Upgrade lark-cli and retry.`, 0, "");
@@ -88275,7 +88362,7 @@ function userIdentityUnavailable(error) {
88275
88362
  async function fetchDocsResponse(input, docsFetchPlan, runner2, identity) {
88276
88363
  const chunks = [];
88277
88364
  let contentFormat;
88278
- let title;
88365
+ let title2;
88279
88366
  let revisionId;
88280
88367
  let unsupportedShape;
88281
88368
  const assets = [];
@@ -88305,7 +88392,7 @@ async function fetchDocsResponse(input, docsFetchPlan, runner2, identity) {
88305
88392
  }
88306
88393
  contentFormat = extracted.format;
88307
88394
  if (page === 0 && extracted.title !== undefined)
88308
- title = extracted.title;
88395
+ title2 = extracted.title;
88309
88396
  revisionId ??= extractDocsFetchRevisionId(payload);
88310
88397
  assets.push(...extractDocsFetchAssets(payload));
88311
88398
  if (extracted.body !== undefined && extracted.body.length > 0) {
@@ -88327,19 +88414,19 @@ async function fetchDocsResponse(input, docsFetchPlan, runner2, identity) {
88327
88414
  throw new LarkCliError(`${LARK_BIN} docs +fetch exceeded ${MAX_FETCH_PAGES} pagination calls; likely a server-side issue`, 0, "");
88328
88415
  }
88329
88416
  }
88330
- const body = chunks.join(`
88417
+ const body2 = chunks.join(`
88331
88418
 
88332
88419
  `);
88333
- if (body.trim().length === 0 && (title === undefined || title.length === 0)) {
88420
+ if (body2.trim().length === 0 && (title2 === undefined || title2.length === 0)) {
88334
88421
  if (unsupportedShape !== undefined) {
88335
88422
  throw new LarkCliError(`${LARK_BIN} docs +fetch returned an unsupported payload shape (${unsupportedShape}). Expected data.markdown or data.document.content; this is a format adapter issue, not a permission error.`, 0, "");
88336
88423
  }
88337
88424
  throw new LarkCliError("document is empty — it may not exist or you lack permission", 0, "");
88338
88425
  }
88339
88426
  return {
88340
- body,
88427
+ body: body2,
88341
88428
  contentFormat,
88342
- ...title !== undefined ? { title } : {},
88429
+ ...title2 !== undefined ? { title: title2 } : {},
88343
88430
  ...revisionId !== undefined ? { revisionId } : {},
88344
88431
  assets
88345
88432
  };
@@ -88392,8 +88479,8 @@ async function fetchFeishuDocSnapshot(input, runner2 = defaultRunner) {
88392
88479
  break;
88393
88480
  fetched = await fetchDocsResponse(input, docsFetchPlan, runner2, accessIdentity);
88394
88481
  }
88395
- let body = fetched.body;
88396
- let title = fetched.title;
88482
+ let body2 = fetched.body;
88483
+ let title2 = fetched.title;
88397
88484
  const revisionId = fetched.revisionId;
88398
88485
  const assets = [...fetched.assets];
88399
88486
  let fidelity = emptyFidelityReport();
@@ -88406,8 +88493,8 @@ async function fetchFeishuDocSnapshot(input, runner2 = defaultRunner) {
88406
88493
  items: []
88407
88494
  };
88408
88495
  if (projection !== undefined) {
88409
- body = projection.markdown;
88410
- title ??= projection.title;
88496
+ body2 = projection.markdown;
88497
+ title2 ??= projection.title;
88411
88498
  fidelity = projection.fidelity;
88412
88499
  assets.push({
88413
88500
  path: "source.xml",
@@ -88420,7 +88507,7 @@ async function fetchFeishuDocSnapshot(input, runner2 = defaultRunner) {
88420
88507
  }
88421
88508
  });
88422
88509
  }
88423
- const resources = [...new Map([...projection?.resources ?? [], ...larkMarkdownImageResources(body)].map((resource) => [resource.locator, resource])).values()];
88510
+ const resources = [...new Map([...projection?.resources ?? [], ...larkMarkdownImageResources(body2)].map((resource) => [resource.locator, resource])).values()];
88424
88511
  if (resources.length) {
88425
88512
  const policy = {
88426
88513
  ...DEFAULT_RESOURCE_POLICY,
@@ -88449,8 +88536,8 @@ async function fetchFeishuDocSnapshot(input, runner2 = defaultRunner) {
88449
88536
  });
88450
88537
  }
88451
88538
  });
88452
- body = applyLarkResourceReplacements(body, resources, materialized.replacements);
88453
- body = replaceLarkMarkdownImages(body, materialized.replacements);
88539
+ body2 = applyLarkResourceReplacements(body2, resources, materialized.replacements);
88540
+ body2 = replaceLarkMarkdownImages(body2, materialized.replacements);
88454
88541
  assets.push(...materialized.assets.map((asset) => ({
88455
88542
  path: asset.path,
88456
88543
  bytes: asset.bytes,
@@ -88465,12 +88552,12 @@ async function fetchFeishuDocSnapshot(input, runner2 = defaultRunner) {
88465
88552
  resourceMaterialization,
88466
88553
  resources
88467
88554
  }));
88468
- if (title !== undefined && title.length > 0 && !/^#\s/.test(body)) {
88555
+ if (title2 !== undefined && title2.length > 0 && !/^#\s/.test(body2)) {
88469
88556
  return {
88470
- markdown: `# ${title}
88557
+ markdown: `# ${title2}
88471
88558
 
88472
- ${body}`,
88473
- title,
88559
+ ${body2}`,
88560
+ title: title2,
88474
88561
  ...revisionId !== undefined ? { revisionId } : {},
88475
88562
  assets,
88476
88563
  fidelity,
@@ -88480,8 +88567,8 @@ ${body}`,
88480
88567
  };
88481
88568
  }
88482
88569
  return {
88483
- markdown: body,
88484
- ...title !== undefined ? { title } : {},
88570
+ markdown: body2,
88571
+ ...title2 !== undefined ? { title: title2 } : {},
88485
88572
  ...revisionId !== undefined ? { revisionId } : {},
88486
88573
  assets,
88487
88574
  fidelity,
@@ -88574,8 +88661,8 @@ var init_sensitiveSourceLiteral = __esm(() => {
88574
88661
  var LARK_DOCUMENT_NORMALIZER_VERSION = "lark-document-normalizer.v1";
88575
88662
 
88576
88663
  // src/project/documentCaptureLark.ts
88577
- import { readdir as readdir21, readFile as readFile70 } from "node:fs/promises";
88578
- import { basename as basename10, extname as extname14, join as join90 } from "node:path";
88664
+ import { readdir as readdir22, readFile as readFile72 } from "node:fs/promises";
88665
+ import { basename as basename10, extname as extname14, join as join92 } from "node:path";
88579
88666
  function titleFromMarkdown2(markdown, fallbackPath) {
88580
88667
  const heading2 = markdown.split(`
88581
88668
  `).find((line) => /^#\s+\S/u.test(line));
@@ -88594,7 +88681,7 @@ function countLines3(markdown) {
88594
88681
  }
88595
88682
  async function fileContentMatches(path3, content3) {
88596
88683
  try {
88597
- const current2 = await readFile70(path3);
88684
+ const current2 = await readFile72(path3);
88598
88685
  const expected = typeof content3 === "string" ? Buffer.from(content3, "utf8") : Buffer.from(content3);
88599
88686
  return current2.equals(expected);
88600
88687
  } catch {
@@ -88602,7 +88689,7 @@ async function fileContentMatches(path3, content3) {
88602
88689
  }
88603
88690
  }
88604
88691
  function sourceManifestPath2(entry) {
88605
- return entry.snapshot?.manifest ?? join90(entry.materializedAt, "manifest.json");
88692
+ return entry.snapshot?.manifest ?? join92(entry.materializedAt, "manifest.json");
88606
88693
  }
88607
88694
  function larkRuntimeError(message, detail) {
88608
88695
  return new ContextError(ExitCode.ExternalToolError, message, {
@@ -88728,12 +88815,12 @@ function assetManifestEntry(asset, assetRoot) {
88728
88815
  };
88729
88816
  }
88730
88817
  async function listSnapshotAssetFiles(root2, assetRoot) {
88731
- const assetsRoot = join90(root2, assetRoot);
88818
+ const assetsRoot = join92(root2, assetRoot);
88732
88819
  const files = [];
88733
88820
  const visit4 = async (dir, prefix = assetRoot) => {
88734
88821
  let entries2;
88735
88822
  try {
88736
- entries2 = await readdir21(dir, { withFileTypes: true });
88823
+ entries2 = await readdir22(dir, { withFileTypes: true });
88737
88824
  } catch (error) {
88738
88825
  if (error instanceof Error && "code" in error && error.code === "ENOENT")
88739
88826
  return;
@@ -88741,7 +88828,7 @@ async function listSnapshotAssetFiles(root2, assetRoot) {
88741
88828
  }
88742
88829
  for (const entry of entries2) {
88743
88830
  const relPath = `${prefix}/${entry.name}`;
88744
- const absolutePath = join90(dir, entry.name);
88831
+ const absolutePath = join92(dir, entry.name);
88745
88832
  if (entry.isDirectory()) {
88746
88833
  await visit4(absolutePath, relPath);
88747
88834
  continue;
@@ -88756,7 +88843,7 @@ async function listSnapshotAssetFiles(root2, assetRoot) {
88756
88843
  }
88757
88844
  async function staleSnapshotAssetPaths(input) {
88758
88845
  const existingPaths = await listSnapshotAssetFiles(input.materializedAtAbsPath, input.assetRoot);
88759
- return existingPaths.filter((path3) => !input.currentPaths.has(path3)).map((path3) => join90(input.materializedAtAbsPath, path3));
88846
+ return existingPaths.filter((path3) => !input.currentPaths.has(path3)).map((path3) => join92(input.materializedAtAbsPath, path3));
88760
88847
  }
88761
88848
  function normalizeLarkError(error, sourceName) {
88762
88849
  if (error instanceof ContextError)
@@ -88838,7 +88925,7 @@ async function runCaptureLarkPhaseUnlocked(input) {
88838
88925
  });
88839
88926
  }
88840
88927
  const documentPath = normalizeSnapshotRelativePath(entry.module === undefined ? "index.md" : `${entry.module}.md`);
88841
- const title = entry.title ?? fetched.title ?? titleFromMarkdown2(normalized, documentPath);
88928
+ const title2 = entry.title ?? fetched.title ?? titleFromMarkdown2(normalized, documentPath);
88842
88929
  const locator = target.kind === "url" ? target.value : `${target.kind}:${target.value}`;
88843
88930
  const assetRoot = entry.module === undefined ? "assets" : `assets/${entry.module}`;
88844
88931
  const reportPath = normalizeSnapshotRelativePath(`${assetRoot}/capture-report.json`);
@@ -88860,13 +88947,13 @@ async function runCaptureLarkPhaseUnlocked(input) {
88860
88947
  const snapshotFiles = [{
88861
88948
  path: documentPath,
88862
88949
  bytes: normalized,
88863
- title,
88950
+ title: title2,
88864
88951
  locator
88865
88952
  }];
88866
88953
  const manifestPath = sourceManifestPath2(entry);
88867
- const manifestAbsPath = join90(input.projectRoot, manifestPath);
88954
+ const manifestAbsPath = join92(input.projectRoot, manifestPath);
88868
88955
  const materializedAt = entry.materializedAt;
88869
- const materializedAtAbsPath = join90(input.projectRoot, materializedAt);
88956
+ const materializedAtAbsPath = join92(input.projectRoot, materializedAt);
88870
88957
  const manifest = createDocumentSnapshotManifest({
88871
88958
  sourceType: "lark",
88872
88959
  sourceName: resolved.sourceName,
@@ -88900,13 +88987,13 @@ async function runCaptureLarkPhaseUnlocked(input) {
88900
88987
  }));
88901
88988
  try {
88902
88989
  const requestedWrites = [{
88903
- path: join90(materializedAtAbsPath, documentPath),
88990
+ path: join92(materializedAtAbsPath, documentPath),
88904
88991
  bytes: normalized
88905
88992
  }];
88906
88993
  for (const asset of assets) {
88907
88994
  if (asset.bytes !== undefined) {
88908
88995
  requestedWrites.push({
88909
- path: join90(materializedAtAbsPath, asset.entry.path),
88996
+ path: join92(materializedAtAbsPath, asset.entry.path),
88910
88997
  bytes: asset.bytes
88911
88998
  });
88912
88999
  }
@@ -88923,7 +89010,7 @@ async function runCaptureLarkPhaseUnlocked(input) {
88923
89010
  currentPaths: new Set(assets.filter((asset) => asset.bytes !== undefined).map((asset) => asset.entry.path))
88924
89011
  });
88925
89012
  await applyAtomicFileBatch({
88926
- transactionRoot: join90(input.projectRoot, ".tmp", "context-runtime", "capture-transactions"),
89013
+ transactionRoot: join92(input.projectRoot, ".tmp", "context-runtime", "capture-transactions"),
88927
89014
  writes,
88928
89015
  removals
88929
89016
  });
@@ -88983,7 +89070,7 @@ async function runCaptureLarkPhaseUnlocked(input) {
88983
89070
  },
88984
89071
  documents: [{
88985
89072
  path: documentPath,
88986
- title,
89073
+ title: title2,
88987
89074
  line_count: countLines3(normalized)
88988
89075
  }],
88989
89076
  assets: assets.map((asset) => ({
@@ -89026,7 +89113,7 @@ __export(exports_actionInputWorkspace, {
89026
89113
  assertActionInputWorkspace: () => assertActionInputWorkspace
89027
89114
  });
89028
89115
  import { realpathSync as realpathSync2 } from "node:fs";
89029
- import { dirname as dirname40, resolve as resolve31 } from "node:path";
89116
+ import { dirname as dirname39, resolve as resolve31 } from "node:path";
89030
89117
  function assertActionInputWorkspace(cwd, inputPath) {
89031
89118
  if (inputPath === "-")
89032
89119
  return;
@@ -89039,7 +89126,7 @@ function assertActionInputWorkspace(cwd, inputPath) {
89039
89126
  } catch {
89040
89127
  return;
89041
89128
  }
89042
- const owner = findContextProjectRoot(dirname40(file));
89129
+ const owner = findContextProjectRoot(dirname39(file));
89043
89130
  if (owner === null || realpathSync2(owner.projectRoot) === realpathSync2(current2.projectRoot))
89044
89131
  return;
89045
89132
  throw new ContextError(ExitCode.WorkspaceStateError, "The completion input belongs to a different Context workspace. Run the workflow CLI and read/write this task's .tmp files in the same intended workspace. No tasks were submitted.", {
@@ -89069,7 +89156,7 @@ __export(exports_articleRetirement, {
89069
89156
  retireArticles: () => retireArticles,
89070
89157
  articleRetirementSchema: () => articleRetirementSchema
89071
89158
  });
89072
- import { readFile as readFile76, readdir as readdir22 } from "node:fs/promises";
89159
+ import { readFile as readFile78, readdir as readdir23 } from "node:fs/promises";
89073
89160
  import { posix as posix10 } from "node:path";
89074
89161
  function rebuildInput(revision) {
89075
89162
  return JSON.stringify({ id: `retirement-${revision.slice(7)}`, operation: "rebuild", timing: "priority", targets: [] });
@@ -89090,7 +89177,7 @@ function invalid2(reason, message, details = {}) {
89090
89177
  }
89091
89178
  async function text9(root2, path3) {
89092
89179
  try {
89093
- return await readFile76(await safeProjectTarget(root2, path3), "utf8");
89180
+ return await readFile78(await safeProjectTarget(root2, path3), "utf8");
89094
89181
  } catch (error) {
89095
89182
  if (error.code === "ENOENT")
89096
89183
  return;
@@ -89128,7 +89215,7 @@ async function retireArticles(input) {
89128
89215
  invalid2("article-retirement-active-review", "Finish the active candidate/revision delivery before retiring approved pages.");
89129
89216
  }
89130
89217
  const before = await text9(input.projectRoot, "knowledge/structure.yaml");
89131
- const structure = before === undefined ? {} : import_yaml41.default.parse(before);
89218
+ const structure = before === undefined ? {} : import_yaml42.default.parse(before);
89132
89219
  const articles = validateArticleStructureEntries(structure.articles ?? []);
89133
89220
  const byPath = new Map(articles.map((article) => [article.path, article]));
89134
89221
  const selected = new Set(value.targets.map((target) => target.path));
@@ -89202,7 +89289,7 @@ async function retireArticles(input) {
89202
89289
  }
89203
89290
  async function inspectTemplates(directory2) {
89204
89291
  const absolute = await safeProjectTarget(input.projectRoot, directory2);
89205
- const entries2 = await readdir22(absolute, { withFileTypes: true }).catch((error) => {
89292
+ const entries2 = await readdir23(absolute, { withFileTypes: true }).catch((error) => {
89206
89293
  if (error.code === "ENOENT")
89207
89294
  return [];
89208
89295
  throw error;
@@ -89251,9 +89338,9 @@ async function retireArticles(input) {
89251
89338
  return [target.replacement ? { ...group, target: { artifact_ref: byPath.get(target.replacement).article_id } } : group];
89252
89339
  });
89253
89340
  const updated = updateKnowledgeMap(map4, { expected_revision: map4.revision, upsert, remove: [] });
89254
- change(KNOWLEDGE_MAP_PATH, await text9(input.projectRoot, KNOWLEDGE_MAP_PATH), import_yaml41.default.stringify(updated));
89341
+ change(KNOWLEDGE_MAP_PATH, await text9(input.projectRoot, KNOWLEDGE_MAP_PATH), import_yaml42.default.stringify(updated));
89255
89342
  }
89256
- change("knowledge/structure.yaml", before, import_yaml41.default.stringify({ ...structure, articles: articles.filter((article) => !selected.has(article.path)) }));
89343
+ change("knowledge/structure.yaml", before, import_yaml42.default.stringify({ ...structure, articles: articles.filter((article) => !selected.has(article.path)) }));
89257
89344
  targets.sort((a, b) => a.path.localeCompare(b.path));
89258
89345
  const revision = indexerProtocolDigest({ value, readDigests, targets });
89259
89346
  const preview = {
@@ -89297,7 +89384,7 @@ async function retireArticles(input) {
89297
89384
  };
89298
89385
  });
89299
89386
  }
89300
- var import_yaml41, articleRetirementSchema, next = "context status --format json";
89387
+ var import_yaml42, articleRetirementSchema, next = "context status --format json";
89301
89388
  var init_articleRetirement = __esm(() => {
89302
89389
  init_zod();
89303
89390
  init_src2();
@@ -89316,7 +89403,7 @@ var init_articleRetirement = __esm(() => {
89316
89403
  init_durableSingleFileTransaction();
89317
89404
  init_durableMultiFileTransaction();
89318
89405
  init_writeLock();
89319
- import_yaml41 = __toESM(require_dist(), 1);
89406
+ import_yaml42 = __toESM(require_dist(), 1);
89320
89407
  articleRetirementSchema = exports_external.object({
89321
89408
  reason: exports_external.string().trim().min(1),
89322
89409
  targets: exports_external.array(exports_external.object({ path: exports_external.string().min(1), replacement: exports_external.string().min(1).optional() }).strict()).min(1)
@@ -89336,14 +89423,14 @@ function schemaOutputFormat(value) {
89336
89423
  }
89337
89424
  function writeSchemaOutput(value, format2) {
89338
89425
  process.stdout.write(format2 === "json" ? `${JSON.stringify(value, null, 2)}
89339
- ` : import_yaml42.default.stringify(value));
89426
+ ` : import_yaml43.default.stringify(value));
89340
89427
  }
89341
- var import_yaml42;
89428
+ var import_yaml43;
89342
89429
  var init_schemaOutput = __esm(() => {
89343
89430
  init_errors3();
89344
89431
  init_cliFeedback();
89345
89432
  init_exitCode();
89346
- import_yaml42 = __toESM(require_dist(), 1);
89433
+ import_yaml43 = __toESM(require_dist(), 1);
89347
89434
  });
89348
89435
 
89349
89436
  // src/project/writeLockRecovery.ts
@@ -89352,9 +89439,9 @@ __export(exports_writeLockRecovery, {
89352
89439
  recoverWriterLock: () => recoverWriterLock,
89353
89440
  inspectWriterLock: () => inspectWriterLock
89354
89441
  });
89355
- import { lstat as lstat10, mkdir as mkdir33, readFile as readFile77, readdir as readdir23, rename as rename8, rmdir as rmdir2 } from "node:fs/promises";
89356
- import { join as join95 } from "node:path";
89357
- import { createHash as createHash28, randomUUID as randomUUID7 } from "node:crypto";
89442
+ import { lstat as lstat10, mkdir as mkdir33, readFile as readFile79, readdir as readdir24, rename as rename8, rmdir as rmdir2 } from "node:fs/promises";
89443
+ import { join as join97 } from "node:path";
89444
+ import { createHash as createHash29, randomUUID as randomUUID7 } from "node:crypto";
89358
89445
  async function inspectWriterLock(root2) {
89359
89446
  const path3 = await safeProjectTarget(root2, lockRelative);
89360
89447
  let stat10;
@@ -89368,7 +89455,7 @@ async function inspectWriterLock(root2) {
89368
89455
  if (!stat10.isDirectory() || stat10.isSymbolicLink())
89369
89456
  throw new Error("Writer lock must be a real directory.");
89370
89457
  const ownerPath = await safeProjectTarget(root2, `${lockRelative}/owner.json`);
89371
- const bytes = await readFile77(ownerPath, "utf8");
89458
+ const bytes = await readFile79(ownerPath, "utf8");
89372
89459
  const owner = JSON.parse(bytes);
89373
89460
  if (owner.protocol !== "context.project-write-lock.v1" || !Number.isSafeInteger(owner.pid) || owner.pid <= 0) {
89374
89461
  throw new Error("Writer lock owner is invalid; preserve the lock for diagnosis.");
@@ -89380,7 +89467,7 @@ async function inspectWriterLock(root2) {
89380
89467
  const code3 = error.code;
89381
89468
  processState = code3 === "ESRCH" ? "not-running" : code3 === "EPERM" ? "running" : "unknown";
89382
89469
  }
89383
- const digest6 = `sha256:${createHash28("sha256").update(`${stat10.dev}:${stat10.ino}:${stat10.birthtimeMs}:${bytes}`).digest("hex")}`;
89470
+ const digest6 = `sha256:${createHash29("sha256").update(`${stat10.dev}:${stat10.ino}:${stat10.birthtimeMs}:${bytes}`).digest("hex")}`;
89384
89471
  return { ...owner, process_state: processState, digest: digest6, path: lockRelative };
89385
89472
  }
89386
89473
  async function recoverWriterLock(input) {
@@ -89400,19 +89487,19 @@ async function recoverWriterLock(input) {
89400
89487
  };
89401
89488
  if (input.plan_digest !== before.digest)
89402
89489
  throw new Error("Writer lock changed; preview recovery again.");
89403
- const path3 = join95(input.projectRoot, lockRelative);
89404
- const guard = join95(path3, ".recovery");
89490
+ const path3 = join97(input.projectRoot, lockRelative);
89491
+ const guard = join97(path3, ".recovery");
89405
89492
  await mkdir33(guard);
89406
89493
  let archived = false;
89407
89494
  try {
89408
89495
  const current2 = await inspectWriterLock(input.projectRoot);
89409
89496
  if (current2?.digest !== before.digest || current2.process_state !== "not-running")
89410
89497
  throw new Error("Writer lock changed or owner resumed; keep the lock.");
89411
- const names = await readdir23(path3);
89498
+ const names = await readdir24(path3);
89412
89499
  if (names.some((name3) => name3 !== "owner.json" && name3 !== ".recovery"))
89413
89500
  throw new Error("Unexpected lock contents; preserve for diagnosis.");
89414
89501
  const archive = `.tmp/context-runtime/locks/recovered-write-${randomUUID7()}.lock`;
89415
- await rename8(path3, join95(input.projectRoot, archive));
89502
+ await rename8(path3, join97(input.projectRoot, archive));
89416
89503
  archived = true;
89417
89504
  return { action: "writer-lock-recovered", archived_lock: archive, next: "context task recover --format json" };
89418
89505
  } finally {
@@ -89436,12 +89523,12 @@ __export(exports_taskRecovery, {
89436
89523
  RECOVERY_COMMAND: () => RECOVERY_COMMAND
89437
89524
  });
89438
89525
  import { existsSync as existsSync26 } from "node:fs";
89439
- import { dirname as dirname41, join as join96 } from "node:path";
89440
- import { lstat as lstat11, readdir as readdir24, readFile as readFile78 } from "node:fs/promises";
89526
+ import { dirname as dirname40, join as join98 } from "node:path";
89527
+ import { lstat as lstat11, readdir as readdir25, readFile as readFile80 } from "node:fs/promises";
89441
89528
  async function recoveryText(root2, path3) {
89442
89529
  const target = await safeProjectTarget(root2, path3);
89443
89530
  try {
89444
- return await readFile78(target, "utf8");
89531
+ return await readFile80(target, "utf8");
89445
89532
  } catch (error) {
89446
89533
  if (error.code === "ENOENT")
89447
89534
  return;
@@ -89454,7 +89541,7 @@ async function recoveryJournals(root2) {
89454
89541
  await safeProjectTarget(root2, path3);
89455
89542
  let stat10;
89456
89543
  try {
89457
- stat10 = await lstat11(join96(root2, path3));
89544
+ stat10 = await lstat11(join98(root2, path3));
89458
89545
  } catch (error) {
89459
89546
  if (error.code === "ENOENT")
89460
89547
  return;
@@ -89465,7 +89552,7 @@ async function recoveryJournals(root2) {
89465
89552
  if (stat10.isDirectory()) {
89466
89553
  if (depth > 3)
89467
89554
  throw new TypeError("Unexpected transaction directory depth; preserve it for diagnosis.");
89468
- for (const name3 of (await readdir24(join96(root2, path3))).sort())
89555
+ for (const name3 of (await readdir25(join98(root2, path3))).sort())
89469
89556
  await visit4(`${path3}/${name3}`, depth + 1);
89470
89557
  } else if (stat10.isFile())
89471
89558
  entries2.push({ path: path3, digest: indexerProtocolDigest(await recoveryText(root2, path3)) });
@@ -89475,10 +89562,10 @@ async function recoveryJournals(root2) {
89475
89562
  }
89476
89563
  function recoveryResources() {
89477
89564
  try {
89478
- const root2 = dirname41(contextWorkflowProviderPath());
89565
+ const root2 = dirname40(contextWorkflowProviderPath());
89479
89566
  const resources = {
89480
- skill: join96(root2, "skills/recover-workspace/SKILL.md"),
89481
- issue_template: join96(root2, "resources/templates/recovery-issue.md")
89567
+ skill: join98(root2, "skills/recover-workspace/SKILL.md"),
89568
+ issue_template: join98(root2, "resources/templates/recovery-issue.md")
89482
89569
  };
89483
89570
  if (!Object.values(resources).every((path3) => existsSync26(path3)))
89484
89571
  throw new Error("Recovery resources are absent from this Provider.");
@@ -89563,8 +89650,8 @@ var exports_taskLocalSourceAdjustment = {};
89563
89650
  __export(exports_taskLocalSourceAdjustment, {
89564
89651
  adjustLocalRevisionSources: () => adjustLocalRevisionSources
89565
89652
  });
89566
- import { readFile as readFile79 } from "node:fs/promises";
89567
- import { join as join97 } from "node:path";
89653
+ import { readFile as readFile81 } from "node:fs/promises";
89654
+ import { join as join99 } from "node:path";
89568
89655
  async function adjustLocalRevisionSources(root2, input) {
89569
89656
  const { readMaintenance: readMaintenance2 } = await Promise.resolve().then(() => (init_maintenanceStorage(), exports_maintenanceStorage));
89570
89657
  if ((await readMaintenance2(root2)).active && await readProductionStage(root2))
@@ -89588,7 +89675,7 @@ async function adjustLocalRevisionSources(root2, input) {
89588
89675
  if (input.refresh && (!current2.refresh_sources || indexerProtocolDigest([...current2.refresh_sources].sort()) !== indexerProtocolDigest([...selected].sort()))) {
89589
89676
  throw new TypeError("No matching acquisition adjustment exists. Run task adjust without refresh first.");
89590
89677
  }
89591
- const raw = await readFile79(join97(root2, await revisionStoragePath(root2)), "utf8");
89678
+ const raw = await readFile81(join99(root2, await revisionStoragePath(root2)), "utf8");
89592
89679
  let next2;
89593
89680
  const discardIds = new Set;
89594
89681
  if (!input.refresh) {
@@ -89691,7 +89778,7 @@ ${input.instruction}` : revision.instruction
89691
89778
  content: content3
89692
89779
  }];
89693
89780
  if (discardIds.size > 0) {
89694
- const ledger = await readFile79(join97(root2, CANDIDATE_LEDGER_FILE), "utf8").catch((error) => {
89781
+ const ledger = await readFile81(join99(root2, CANDIDATE_LEDGER_FILE), "utf8").catch((error) => {
89695
89782
  if (error.code === "ENOENT")
89696
89783
  return;
89697
89784
  throw error;
@@ -89813,7 +89900,7 @@ var init_taskSourceAdjustment = __esm(() => {
89813
89900
  });
89814
89901
 
89815
89902
  // src/project/managedDocumentImport.ts
89816
- import { readFile as readFile85 } from "node:fs/promises";
89903
+ import { readFile as readFile87 } from "node:fs/promises";
89817
89904
  async function importManagedDocument(projectRoot, value) {
89818
89905
  const input = inputSchema.parse(value);
89819
89906
  if (input.type !== "sessions" && input.changes !== undefined)
@@ -89822,7 +89909,7 @@ async function importManagedDocument(projectRoot, value) {
89822
89909
  const path3 = await assertManagedDocumentPath(projectRoot, input.type, input.name);
89823
89910
  let previous3;
89824
89911
  try {
89825
- previous3 = await readFile85(path3, "utf8");
89912
+ previous3 = await readFile87(path3, "utf8");
89826
89913
  } catch (error) {
89827
89914
  if (!(error && typeof error === "object" && ("code" in error) && error.code === "ENOENT"))
89828
89915
  throw error;
@@ -89869,14 +89956,14 @@ var init_managedDocumentImport = __esm(() => {
89869
89956
  });
89870
89957
 
89871
89958
  // src/project/larkDocumentImport.ts
89872
- import { readFile as readFile86 } from "node:fs/promises";
89959
+ import { readFile as readFile88 } from "node:fs/promises";
89873
89960
  async function importLarkDocument(projectRoot, value) {
89874
89961
  const input = schema3.parse(value);
89875
89962
  const responsePages = [];
89876
89963
  for (const path3 of input.response_files) {
89877
89964
  assertActionInputWorkspace(projectRoot, path3);
89878
89965
  const { resolve: resolve8 } = await import("node:path");
89879
- responsePages.push(await readFile86(resolve8(projectRoot, path3), "utf8"));
89966
+ responsePages.push(await readFile88(resolve8(projectRoot, path3), "utf8"));
89880
89967
  }
89881
89968
  const mediaFiles = {};
89882
89969
  for (const [token, path3] of Object.entries(input.media_files ?? {})) {
@@ -89944,11 +90031,11 @@ var exports_managedDocumentRename = {};
89944
90031
  __export(exports_managedDocumentRename, {
89945
90032
  renameManagedDocument: () => renameManagedDocument
89946
90033
  });
89947
- import { readFile as readFile87 } from "node:fs/promises";
89948
- import { join as join105, posix as posix11 } from "node:path";
90034
+ import { readFile as readFile89 } from "node:fs/promises";
90035
+ import { join as join107, posix as posix11 } from "node:path";
89949
90036
  async function optionalText2(path3) {
89950
90037
  try {
89951
- return await readFile87(path3, "utf8");
90038
+ return await readFile89(path3, "utf8");
89952
90039
  } catch (error) {
89953
90040
  if (error && typeof error === "object" && "code" in error && error.code === "ENOENT")
89954
90041
  return;
@@ -90032,12 +90119,12 @@ async function renameManagedDocument(input) {
90032
90119
  if (path3.split("/").includes("..") || path3.startsWith("/"))
90033
90120
  throw new TypeError("Source references contain an unsafe knowledge path; repair it before renaming.");
90034
90121
  await safeProjectTarget(input.projectRoot, path3);
90035
- const before = await optionalText2(join105(input.projectRoot, path3));
90122
+ const before = await optionalText2(join107(input.projectRoot, path3));
90036
90123
  if (before === undefined)
90037
90124
  continue;
90038
90125
  let after;
90039
90126
  if (path3.endsWith(".yaml")) {
90040
- const parsed = import_yaml48.default.parse(before);
90127
+ const parsed = import_yaml49.default.parse(before);
90041
90128
  const updated = replace2(parsed);
90042
90129
  if (path3 === "knowledge/structure.yaml") {
90043
90130
  const rewritten = articles.map((article) => ({ ...article, sections: article.sections.map((section) => ({
@@ -90052,7 +90139,7 @@ async function renameManagedDocument(input) {
90052
90139
  }
90053
90140
  if (JSON.stringify(parsed) === JSON.stringify(updated))
90054
90141
  continue;
90055
- after = import_yaml48.default.stringify(updated);
90142
+ after = import_yaml49.default.stringify(updated);
90056
90143
  } else
90057
90144
  after = before.replace(reference2, nextRef);
90058
90145
  if (path3 === "src/index.ts")
@@ -90104,7 +90191,7 @@ async function renameManagedDocument(input) {
90104
90191
  };
90105
90192
  });
90106
90193
  }
90107
- var import_yaml48;
90194
+ var import_yaml49;
90108
90195
  var init_managedDocumentRename = __esm(() => {
90109
90196
  init_markdownLinks();
90110
90197
  init_src2();
@@ -90116,12 +90203,12 @@ var init_managedDocumentRename = __esm(() => {
90116
90203
  init_durableSingleFileTransaction();
90117
90204
  init_durableMultiFileTransaction();
90118
90205
  init_writeLock();
90119
- import_yaml48 = __toESM(require_dist(), 1);
90206
+ import_yaml49 = __toESM(require_dist(), 1);
90120
90207
  });
90121
90208
 
90122
90209
  // src/cli.ts
90123
90210
  import { existsSync as existsSync36, realpathSync as realpathSync3 } from "node:fs";
90124
- import { dirname as dirname48, join as join109 } from "node:path";
90211
+ import { dirname as dirname47, join as join111 } from "node:path";
90125
90212
  import { fileURLToPath as fileURLToPath10, pathToFileURL as pathToFileURL3 } from "node:url";
90126
90213
 
90127
90214
  // ../../node_modules/.bun/commander@11.1.0/node_modules/commander/esm.mjs
@@ -90176,7 +90263,7 @@ init_dist();
90176
90263
  init_cliFeedback();
90177
90264
  init_errors3();
90178
90265
  init_exitCode();
90179
- import { join as join87 } from "node:path";
90266
+ import { join as join89 } from "node:path";
90180
90267
 
90181
90268
  // src/project/status.ts
90182
90269
  init_productionPlanning();
@@ -101011,200 +101098,21 @@ init_workflowFacts();
101011
101098
  init_workflowProvider();
101012
101099
  init_workflowTypes();
101013
101100
 
101014
- // src/project/reviewHtmlTranslations.ts
101015
- var REVIEW_UI_ZH = {
101016
- "Context Review": "Context 内容审核",
101017
- "All approved": "全部批准",
101018
- "Omit all": "全部不收录",
101019
- "Copy review results": "复制审核结果",
101020
- "Toggle theme": "切换明暗主题",
101021
- "Pages to review": "待审页面",
101022
- "candidate filters": "按审核状态筛选",
101023
- approved: "已批准",
101024
- omitted: "不收录",
101025
- pending: "待审核",
101026
- "Search pages or modules": "搜索页面或模块",
101027
- "Page content": "页面内容",
101028
- Previous: "上一页",
101029
- Next: "下一页",
101030
- "Next pending": "下一个待审",
101031
- "Review results": "审核结果",
101032
- "These choices take effect only after you send the review code back to the conversation. Each segment is at most 980 characters. Send every segment before applying.": "将审核码发回会话后,这些选择才会生效。每段不超过 980 个字符;如果有多段,请全部发送后再应用。",
101033
- "Previous segment": "上一段",
101034
- "Next segment": "下一段",
101035
- "review code": "审核码",
101036
- Close: "关闭",
101037
- Copy: "复制",
101038
- "{count} pages · {scope} · {pending} pending · {approved} approved · {rejected} omitted": "{count} 页 · {scope} · {pending} 待审核 · {approved} 已批准 · {rejected} 不收录",
101039
- "All collections": "全部分类",
101040
- "Set all {count} pages in {group} to {status}?": "将 {group} 的全部 {count} 页设为“{status}”?",
101041
- "Set all {count} pages to {status}?": "将全部 {count} 页设为“{status}”?",
101042
- "Choices changed. Copy the updated code before applying.": "选择已变更,请复制更新后的审核码再应用。",
101043
- "Select at least one page decision; pending pages remain for later review.": "请至少选择一页的审核结果;待审页面留待后续处理。",
101044
- "Segment {part}/{total} · {length}/980 characters": "第 {part}/{total} 段 · {length}/980 字符",
101045
- "No review code yet": "暂未生成审核码",
101046
- "{approved} approved · {rejected} not included · {pending} pending": "{approved} 页批准 · {rejected} 页不收录 · {pending} 页待审核",
101047
- "Pages not included": "不收录的页面",
101048
- "Open review results": "查看审核结果",
101049
- "{count} pending pages remain": "还有 {count} 页待审核",
101050
- Copied: "已复制",
101051
- "Copy manually from the textarea": "请从文本框中手动复制",
101052
- "No draft candidates.": "当前没有待审页面。",
101053
- "Nothing to review.": "没有需要审核的内容。",
101054
- "No candidates match the current filters.": "没有符合当前筛选条件的页面。",
101055
- "Adjust the candidate filters to continue reviewing.": "请调整筛选条件,继续审核。",
101056
- "{count} items": "{count} 页",
101057
- "evidence unavailable": "来源不可用",
101058
- "Source snapshot unavailable. Restore it before approving this candidate, or omit the page.": "来源快照不可用。请先恢复来源再批准,或选择不收录此页。",
101059
- "Source Markdown": "Markdown 原文",
101060
- "Source locations": "来源位置",
101061
- Approve: "批准",
101062
- Omit: "不收录",
101063
- "Need changes? Leave this page pending and ask the agent to repair it. Other reviewed pages can be approved.": "需要修改?本页保留待审,并让 Agent 返修。已审核的其他页面可以先批准。"
101064
- };
101065
-
101066
- // src/project/reviewCode.ts
101067
- function createReviewCodeCodec() {
101068
- const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
101069
- function checksum(text7) {
101070
- let crc = 4294967295;
101071
- for (let i2 = 0;i2 < text7.length; i2++) {
101072
- crc ^= text7.charCodeAt(i2);
101073
- for (let bit = 0;bit < 8; bit++)
101074
- crc = crc >>> 1 ^ (crc & 1 ? 3988292384 : 0);
101075
- }
101076
- return ((crc ^ 4294967295) >>> 0).toString(16).padStart(8, "0");
101077
- }
101078
- function pack(bytes) {
101079
- let value = 0, bits = 0, result = "";
101080
- for (const byte of bytes) {
101081
- value = value << 8 | byte;
101082
- bits += 8;
101083
- while (bits >= 6) {
101084
- bits -= 6;
101085
- result += alphabet[value >>> bits & 63];
101086
- }
101087
- }
101088
- if (bits)
101089
- result += alphabet[value << 6 - bits & 63];
101090
- return result;
101091
- }
101092
- function unpack(text7) {
101093
- if (!/^[A-Za-z0-9_-]*$/.test(text7))
101094
- throw new Error("Invalid review code encoding");
101095
- let value = 0, bits = 0;
101096
- const bytes = [];
101097
- for (const char of text7) {
101098
- value = value << 6 | alphabet.indexOf(char);
101099
- bits += 6;
101100
- if (bits >= 8) {
101101
- bits -= 8;
101102
- bytes.push(value >>> bits & 255);
101103
- }
101104
- }
101105
- if (pack(bytes) !== text7)
101106
- throw new Error("Noncanonical review code encoding");
101107
- return bytes;
101108
- }
101109
- function hash3(text7) {
101110
- if (!/^[a-f0-9]{64}$/.test(text7))
101111
- throw new Error("Review requires a complete candidate digest");
101112
- return pack(Array.from({ length: 32 }, (_, i2) => Number.parseInt(text7.slice(i2 * 2, i2 * 2 + 2), 16)));
101113
- }
101114
- function unhash(text7) {
101115
- const bytes = unpack(text7);
101116
- if (bytes.length !== 32)
101117
- throw new Error("Invalid candidate digest");
101118
- return bytes.map((byte) => byte.toString(16).padStart(2, "0")).join("");
101119
- }
101120
- function encode(scope2, idsHash, contentHash2, statuses) {
101121
- if (!/^[a-z][a-z0-9-]*$/.test(scope2) || !statuses.length || statuses.length > 1e6 || statuses.some((status) => status !== "approved" && status !== "rejected" && status !== "pending")) {
101122
- throw new Error("Select at least one review decision and keep undecided pages pending");
101123
- }
101124
- if (statuses.every((s) => s === "pending"))
101125
- throw new Error("Select at least one review decision");
101126
- const mode = statuses.every((s) => s === "approved") ? "a" : statuses.every((s) => s === "rejected") ? "r" : statuses.includes("pending") ? "p" : "b";
101127
- const bytes = Array(Math.ceil(statuses.length / (mode === "p" ? 4 : 8))).fill(0);
101128
- if (mode === "p")
101129
- statuses.forEach((s, i2) => {
101130
- bytes[i2 >> 2] |= (s === "approved" ? 1 : s === "rejected" ? 2 : 0) << i2 % 4 * 2;
101131
- });
101132
- if (mode === "b")
101133
- statuses.forEach((s, i2) => {
101134
- if (s === "rejected")
101135
- bytes[i2 >> 3] |= 1 << i2 % 8;
101136
- });
101137
- const body = ["CR1", scope2, statuses.length, hash3(idsHash), hash3(contentHash2), mode, mode === "b" || mode === "p" ? pack(bytes) : ""].join(".");
101138
- const code = `${body}.${checksum(body)}`;
101139
- if (code.length <= 980)
101140
- return [code];
101141
- const total = Math.ceil(code.length / 900);
101142
- if (total > 200)
101143
- throw new Error("Review decisions exceed 200 segments; use a smaller collection scope");
101144
- const identity = checksum(code);
101145
- return Array.from({ length: total }, (_, i2) => `CRP1.${identity}.${i2 + 1}.${total}.${code.slice(i2 * 900, (i2 + 1) * 900)}`);
101146
- }
101147
- function decode2(input) {
101148
- if (input.length > 250000)
101149
- throw new Error("Review code exceeds the supported size");
101150
- const lines = input.trim().split(/\s+/);
101151
- let code = lines[0];
101152
- if (code.startsWith("CRP1.")) {
101153
- const parts = new Map;
101154
- let identity = "", total = 0;
101155
- for (const line of lines) {
101156
- const match = /^CRP1\.([a-f0-9]{8})\.([1-9][0-9]*)\.([1-9][0-9]*)\.(.+)$/.exec(line);
101157
- if (!match || line.length > 980)
101158
- throw new Error("Invalid review code segment");
101159
- const index2 = Number(match[2]), count2 = Number(match[3]);
101160
- if (count2 > 200 || index2 > count2 || parts.has(index2) || total && (total !== count2 || identity !== match[1])) {
101161
- throw new Error("Duplicate or mixed review code segments");
101162
- }
101163
- identity = match[1];
101164
- total = count2;
101165
- parts.set(index2, match[4]);
101166
- }
101167
- if (parts.size !== total)
101168
- throw new Error(`Missing review code segments: received ${parts.size} of ${total}; collect all segments before applying`);
101169
- code = Array.from({ length: total }, (_, i2) => parts.get(i2 + 1)).join("");
101170
- if (checksum(code) !== identity)
101171
- throw new Error("Review code segment checksum mismatch");
101172
- } else if (lines.length !== 1 || code.length > 980)
101173
- throw new Error("Copy each complete review code segment unchanged");
101174
- const fields = code.split(".");
101175
- if (fields.length !== 8 || fields[0] !== "CR1" || checksum(fields.slice(0, 7).join(".")) !== fields[7]) {
101176
- throw new Error("Review code is damaged or unsupported; copy it again from the report");
101177
- }
101178
- const [, scope2, countText, ids, content3, mode, data2] = fields;
101179
- if (!/^[a-z][a-z0-9-]*$/.test(scope2) || !/^[1-9][0-9]*$/.test(countText))
101180
- throw new Error("Invalid review scope");
101181
- const count = Number(countText);
101182
- if (count > 1e6 || !["a", "r", "b", "p"].includes(mode))
101183
- throw new Error("Invalid review decisions");
101184
- const bytes = unpack(data2);
101185
- const perByte = mode === "p" ? 4 : 8;
101186
- if (mode === "b" || mode === "p" ? bytes.length !== Math.ceil(count / perByte) || count % perByte !== 0 && bytes.at(-1) >>> count % perByte * (mode === "p" ? 2 : 1) !== 0 : data2 !== "") {
101187
- throw new Error("Invalid review decision bitmap");
101188
- }
101189
- const statuses = Array.from({ length: count }, (_, i2) => {
101190
- if (mode === "p") {
101191
- const value = bytes[i2 >> 2] >>> i2 % 4 * 2 & 3;
101192
- if (value === 3)
101193
- throw new Error("Invalid pending review bitmap");
101194
- return value === 1 ? "approved" : value === 2 ? "rejected" : "pending";
101195
- }
101196
- return mode === "r" || mode === "b" && bytes[i2 >> 3] & 1 << i2 % 8 ? "rejected" : "approved";
101197
- });
101198
- if (statuses.every((s) => s === "pending"))
101199
- throw new Error("Review contains no decisions");
101200
- return { scope: scope2, count, idsHash: unhash(ids), contentHash: unhash(content3), statuses };
101201
- }
101202
- return { encode, decode: decode2 };
101203
- }
101101
+ // src/project/reviewHtml.ts
101102
+ init_candidateLedger();
101103
+ import { mkdir as mkdir30, writeFile as writeFile24 } from "node:fs/promises";
101104
+ import { dirname as dirname37, isAbsolute as isAbsolute15, join as join87, resolve as resolve28 } from "node:path";
101204
101105
 
101205
- // src/project/reviewMarkdown.ts
101106
+ // src/project/reviewSiteModel.ts
101107
+ init_src2();
101206
101108
  init_unified();
101207
101109
  init_remark_parse();
101110
+ var import_yaml40 = __toESM(require_dist(), 1);
101111
+ import { readFile as readFile68 } from "node:fs/promises";
101112
+ import { join as join86 } from "node:path";
101113
+ import { execFile as execFile10 } from "node:child_process";
101114
+ import { promisify as promisify10 } from "node:util";
101115
+ import { createHash as createHash24 } from "node:crypto";
101208
101116
 
101209
101117
  // ../../node_modules/.bun/mdast-util-gfm-autolink-literal@2.0.1/node_modules/mdast-util-gfm-autolink-literal/lib/index.js
101210
101118
  init_development();
@@ -104162,7 +104070,45 @@ function remarkGfm(options) {
104162
104070
  fromMarkdownExtensions.push(gfmFromMarkdown());
104163
104071
  toMarkdownExtensions.push(gfmToMarkdown(settings));
104164
104072
  }
104073
+ // src/project/reviewSiteModel.ts
104074
+ init_workspace();
104075
+
104076
+ // src/project/reviewFeedback.ts
104077
+ import { readdir as readdir20, readFile as readFile67 } from "node:fs/promises";
104078
+ import { join as join85 } from "node:path";
104079
+ async function readPendingReviewFeedback(root2, candidates) {
104080
+ const directory = join85(root2, ".tmp/context-runtime/review-feedback");
104081
+ let files;
104082
+ try {
104083
+ files = await readdir20(directory);
104084
+ } catch (e) {
104085
+ if (e.code === "ENOENT")
104086
+ return [];
104087
+ throw e;
104088
+ }
104089
+ const pending = new Map(candidates.map((c) => [c.record.candidate_id, c.record.fingerprint]));
104090
+ const results = new Map;
104091
+ const receipts = [];
104092
+ for (const file of files.filter((f) => /^[a-f0-9]+\.json$/u.test(f)).sort()) {
104093
+ const receipt2 = JSON.parse(await readFile67(join85(directory, file), "utf8"));
104094
+ receipts.push(receipt2);
104095
+ }
104096
+ for (const receipt2 of receipts.sort((a, b) => a.created_at.localeCompare(b.created_at))) {
104097
+ for (const repair of receipt2.repairs)
104098
+ if (pending.get(repair.candidate_id) === repair.fingerprint)
104099
+ results.set(repair.candidate_id, { candidate_id: repair.candidate_id, path: repair.path, instruction: repair.instruction, command: repair.command });
104100
+ }
104101
+ return [...results.values()];
104102
+ }
104103
+
104104
+ // src/project/reviewSiteModel.ts
104105
+ init_knowledgeMap2();
104106
+ init_approvedKnowledgeMetadata();
104107
+ init_approvedFileRead();
104108
+
104165
104109
  // src/project/reviewMarkdown.ts
104110
+ init_unified();
104111
+ init_remark_parse();
104166
104112
  function escapeReviewHtml(value) {
104167
104113
  return value.replace(/&/gu, "&amp;").replace(/</gu, "&lt;").replace(/>/gu, "&gt;").replace(/"/gu, "&quot;");
104168
104114
  }
@@ -104244,172 +104190,286 @@ function renderReviewMarkdown(markdown, pageTitle) {
104244
104190
  return (pageTitle && !hasPageHeading ? `<h1>${escapeReviewHtml(pageTitle)}</h1>` : "") + render(tree);
104245
104191
  }
104246
104192
 
104247
- // src/project/reviewHtml.ts
104248
- init_candidateLedger();
104249
- import { mkdir as mkdir30, writeFile as writeFile24 } from "node:fs/promises";
104250
- import { dirname as dirname38, isAbsolute as isAbsolute15, join as join85, resolve as resolve28 } from "node:path";
104251
-
104252
- // src/project/reviewHtmlPresentation.ts
104253
- import { dirname as dirname37 } from "node:path";
104254
- function candidatePreview(candidate) {
104255
- if (candidate.snapshot === undefined) {
104256
- return "Evidence unavailable. Restore the committed snapshot before approving this candidate.";
104257
- }
104258
- const markdown = candidate.snapshot.markdown ?? candidate.record.review.summary;
104259
- return markdown.trim().slice(0, 1200);
104260
- }
104261
- function candidateGroupKey(candidate) {
104262
- const path3 = candidate.record.indexer_candidate.sections.flatMap((section) => section.references)[0]?.locator.path;
104263
- const group = path3 === undefined ? candidate.record.kind : dirname37(path3).split("\\").join("/");
104264
- return `${candidate.record.module} / ${group === "." ? candidate.record.kind : group}`;
104265
- }
104266
- function candidateGroupLabel(candidate) {
104267
- const path3 = candidate.record.indexer_candidate.sections.flatMap((section) => section.references)[0]?.locator.path;
104268
- const group = path3 === undefined ? candidate.record.kind : dirname37(path3).split("\\").join("/");
104269
- return group === "." ? candidate.record.module : `${candidate.record.module} · ${group}`;
104270
- }
104271
-
104272
- // src/project/reviewHtmlStyles.ts
104273
- var REVIEW_HTML_STYLES = `
104274
- :root, [data-theme="light"] {
104275
- color-scheme: light;
104276
- --sb-bg-base:#f1f1f2; --sb-bg-surface:#f7f7f7; --sb-bg-elevated:#fdfdfd; --sb-bg-hover:#ebebec; --sb-bg-active:#e1e1e4;
104277
- --sb-text-primary:#181818; --sb-text-secondary:#535353; --sb-text-muted:#717171;
104278
- --sb-border-default:#e5e5e5; --sb-border-strong:#d5d5d5;
104279
- --sb-brand:#ef6f2e; --sb-brand-text:rgba(255,255,255,.85); --sb-brand-border:rgba(255,180,130,.65);
104280
- --sb-success:#059669; --sb-error:#dc2626; --sb-warning:#d97706;
104281
- }
104282
- [data-theme="dark"] {
104283
- color-scheme: dark;
104284
- --sb-bg-base:#181818; --sb-bg-surface:#202020; --sb-bg-elevated:#252525; --sb-bg-hover:#2b2b2b; --sb-bg-active:#343434;
104285
- --sb-text-primary:#fbfbfb; --sb-text-secondary:#b5b5b5; --sb-text-muted:#8a8a8a;
104286
- --sb-border-default:#2f2f2f; --sb-border-strong:#3a3a3a;
104287
- --sb-brand:#ef6f2e; --sb-brand-text:rgba(255,255,255,.85); --sb-brand-border:rgba(255,180,130,.65);
104288
- --sb-success:#3ecf8e; --sb-error:#ef4444; --sb-warning:#f59e0b;
104289
- }
104290
- * { box-sizing: border-box; }
104291
- html, body { height:100%; }
104292
- body { margin:0; overflow:hidden; background:var(--sb-bg-base); color:var(--sb-text-primary); font:14px/1.5 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
104293
- button, textarea { font: inherit; }
104294
- .shell { height:100vh; padding:22px; display:grid; grid-template-rows:auto minmax(0, 1fr); gap:18px; overflow:hidden; }
104295
- .header { display:grid; grid-template-columns:minmax(0, 1fr) auto; align-items:center; gap:16px; }
104296
- .titleline { min-width:0; display:flex; flex-direction:column; align-items:flex-start; gap:4px; }
104297
- #count-state { max-width:100%; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
104298
- .toolbar { display:flex; align-items:center; gap:10px; justify-content:flex-end; white-space:nowrap; }
104299
- .bulk-actions { display:flex; align-items:center; gap:8px; }
104300
- h1 { margin:0; font-size:28px; font-weight:500; letter-spacing:0; }
104301
- .subtle { color:var(--sb-text-secondary); }
104302
- .layout { min-height:0; display:grid; grid-template-columns:minmax(260px, 375px) minmax(0, 1fr); gap:22px; align-items:stretch; }
104303
- .panel { min-height:0; background:var(--sb-bg-surface); border:1px solid var(--sb-border-default); border-radius:8px; overflow:hidden; }
104304
- .candidate-panel { display:grid; grid-template-rows:auto auto minmax(0, 1fr); }
104305
- .detail-panel { display:grid; grid-template-rows:auto minmax(0, 1fr); }
104306
- .panel-head { padding:14px 16px; border-bottom:1px solid var(--sb-border-default); color:var(--sb-text-secondary); font-size:12px; text-transform:uppercase; }
104307
- .candidate-head { display:flex; flex-direction:column; align-items:flex-start; justify-content:space-between; gap:10px; }
104308
- .filters { display:flex; align-items:center; gap:8px; text-transform:none; font-size:12px; }
104309
- .filter { display:inline-flex; align-items:center; gap:5px; cursor:pointer; color:var(--sb-text-secondary); }
104310
- .filter input { margin:0; accent-color:var(--sb-brand); }
104311
- #list { min-height:0; overflow:auto; }
104312
- .candidate-group { border-bottom:1px solid var(--sb-border-default); }
104313
- .candidate-group-title { width:100%; display:grid; grid-template-columns:minmax(0, 1fr) auto; align-items:start; gap:10px; padding:12px 12px 8px 16px; color:var(--sb-text-secondary); font-size:12px; font-weight:600; overflow-wrap:anywhere; cursor:pointer; user-select:none; }
104314
- .group-label { min-width:0; display:grid; grid-template-columns:auto minmax(0, 1fr); align-items:center; gap:7px; }
104315
- .group-key { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
104316
- .group-count { grid-column:2; justify-self:start; margin-top:4px; color:var(--sb-text-muted); font-weight:400; }
104317
- .group-actions { display:grid; justify-items:end; gap:5px; flex:0 0 auto; }
104318
- .group-btn { border:1px solid var(--sb-border-default); border-radius:999px; padding:2px 8px; background:transparent; color:var(--sb-text-secondary); cursor:pointer; font-size:12px; }
104319
- .group-btn:hover { border-color:var(--sb-brand); color:var(--sb-text-primary); }
104320
- .candidate { width:100%; display:block; text-align:left; padding:10px 16px 12px 30px; border:0; color:var(--sb-text-primary); background:transparent; cursor:pointer; }
104321
- .candidate + .candidate { border-top:1px solid var(--sb-border-default); }
104322
- .candidate:hover, .candidate.active { background:var(--sb-bg-hover); }
104323
- .candidate-title { min-width:0; display:flex; align-items:center; justify-content:space-between; gap:10px; }
104324
- .candidate-title-text { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
104325
- .candidate-tags { display:flex; align-items:center; gap:6px; flex:0 0 auto; }
104326
- .candidate-summary { display:-webkit-box; margin-top:5px; overflow:hidden; color:var(--sb-text-muted); font-size:12px; line-height:1.45; -webkit-box-orient:vertical; -webkit-line-clamp:2; }
104327
- .meta { display:flex; gap:8px; flex-wrap:wrap; margin-top:8px; color:var(--sb-text-muted); font-size:12px; }
104328
- .badge { border:1px solid var(--sb-border-strong); border-radius:999px; padding:1px 7px; color:var(--sb-text-secondary); background:transparent; }
104329
- .badge.pending { border-color:rgba(245,158,11,.55); color:var(--sb-warning); }
104330
- .badge.approved { border-color:rgba(62,207,142,.55); color:var(--sb-success); }
104331
- .badge.rejected { border-color:rgba(239,68,68,.55); color:var(--sb-error); }
104332
- .badge.warning { border-color:rgba(245,158,11,.55); color:var(--sb-warning); }
104333
- .notice { padding:10px 12px; border:1px solid var(--sb-border-strong); border-radius:8px; background:var(--sb-bg-elevated); color:var(--sb-text-secondary); }
104334
- .notice.warning { border-color:rgba(245,158,11,.55); color:var(--sb-warning); }
104335
- .section-list { display:grid; gap:12px; }
104336
- .section-card { display:grid; gap:10px; padding:12px; border:1px solid var(--sb-border-default); border-radius:8px; background:var(--sb-bg-elevated); }
104337
- .section-card.empty { color:var(--sb-text-secondary); }
104338
- .section-header { display:flex; align-items:center; justify-content:space-between; gap:10px; flex-wrap:wrap; }
104339
- .section-title { display:flex; align-items:center; gap:8px; flex-wrap:wrap; min-width:0; }
104340
- .section-title code { overflow-wrap:anywhere; }
104341
- .section-summary { color:var(--sb-text-secondary); }
104342
- .section-source-refs { display:grid; gap:5px; margin-top:8px; color:var(--sb-text-muted); font-size:12px; }
104343
- .section-source-refs code { overflow-wrap:anywhere; }
104344
- .section-body { max-height:360px; overflow:auto; }
104345
- .evidence-details, .technical-details { border:1px solid var(--sb-border-default); border-radius:8px; background:var(--sb-bg-surface); }
104346
- .evidence-details > summary, .technical-details > summary { cursor:pointer; padding:8px 10px; color:var(--sb-text-secondary); font-size:12px; }
104347
- .technical-details > pre { border:0; border-top:1px solid var(--sb-border-default); border-radius:0 0 8px 8px; background:transparent; }
104348
- .page-location { min-width:0; color:var(--sb-text-muted); font-size:12px; overflow-wrap:anywhere; }
104349
- .repair-hint { margin:0; font-size:12px; color:var(--sb-text-muted); }
104350
- #modal-copy-state { min-height:1.5em; font-size:12px; }
104351
- .technical-content { display:grid; gap:10px; padding:0 10px 10px; border-top:1px solid var(--sb-border-default); }
104352
- .technical-content .meta { margin-top:10px; }
104353
- .technical-content code { overflow-wrap:anywhere; }
104354
- .detail { min-height:0; overflow:auto; padding:18px; display:grid; align-content:start; gap:16px; }
104355
- .detail-titlebar { display:flex; align-items:flex-start; justify-content:space-between; gap:16px; }
104356
- .detail-heading { min-width:0; display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
104357
- .detail h2 { margin:0; font-size:22px; font-weight:500; letter-spacing:0; overflow-wrap:anywhere; }
104358
- code, pre, textarea { font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
104359
- pre { margin:0; white-space:pre-wrap; overflow:visible; padding:14px; border-radius:8px; border:1px solid var(--sb-border-default); background:var(--sb-bg-elevated); color:var(--sb-text-primary); }
104360
- .actions { display:flex; gap:10px; flex-wrap:wrap; }
104361
- .btn { border:1px solid var(--sb-border-strong); background:transparent; color:var(--sb-text-primary); padding:8px 12px; border-radius:6px; cursor:pointer; }
104362
- .btn:disabled { opacity:.55; cursor:not-allowed; }
104363
- .language-btn { width:76px; flex-shrink:0; }
104364
- .icon-btn { width:40px; height:40px; display:inline-grid; place-items:center; padding:0; font-size:18px; }
104365
- .btn:hover { border-color:var(--sb-brand); }
104366
- .btn.primary { background:var(--sb-brand); border-color:var(--sb-brand-border); color:var(--sb-brand-text); }
104367
- .btn.brand { background:var(--sb-brand); border-color:var(--sb-brand-border); color:var(--sb-brand-text); }
104368
- .btn.brand.ready { filter:brightness(1.04); }
104369
- .btn.reject.active { border-color:var(--sb-error); color:var(--sb-error); }
104370
- .btn.approve.active { border-color:var(--sb-success); color:var(--sb-success); }
104371
- textarea { width:100%; min-height:120px; resize:vertical; border:1px solid var(--sb-border-strong); border-radius:8px; padding:12px; background:var(--sb-bg-elevated); color:var(--sb-text-primary); }
104372
- .empty { padding:28px; color:var(--sb-text-secondary); border:1px dashed var(--sb-border-strong); border-radius:8px; }
104373
- .modal { position:fixed; inset:0; z-index:10; display:grid; place-items:center; padding:24px; background:rgba(0,0,0,.45); }
104374
- .modal.hidden { display:none; }
104375
- .modal-card { width:min(920px, 100%); max-height:min(720px, 92vh); display:grid; grid-template-rows:auto minmax(0, 1fr) auto; gap:14px; border:1px solid var(--sb-border-default); border-radius:8px; background:var(--sb-bg-surface); padding:18px; box-shadow:0 16px 44px rgba(0,0,0,.22); }
104376
- .modal-card h2 { margin:0; font-size:20px; font-weight:500; letter-spacing:0; }
104377
- .modal-body { min-height:0; overflow:auto; }
104378
- .modal-actions { display:flex; justify-content:flex-end; align-items:center; gap:10px; }
104379
- #search { margin:10px; min-width:0; padding:9px; border:1px solid var(--sb-border-default); border-radius:6px; background:var(--sb-bg-elevated); color:var(--sb-text-primary); }
104380
- .reader-navigation, .code-navigation { display:flex; justify-content:space-between; align-items:center; gap:8px; flex-wrap:wrap; }
104381
- .code-navigation { margin:12px 0; }
104382
- .code-navigation[hidden] { display:none; }
104383
- .reader-body { min-width:0; line-height:1.7; overflow-wrap:anywhere; }
104384
- .reader-body h1 { font-size:26px; }
104385
- .reader-body h2, .reader-body h3 { margin:24px 0 10px; }
104386
- .reader-body details { margin:12px 0; }
104387
- .reader-body summary { cursor:pointer; font-weight:500; }
104388
- .reader-body p { margin:10px 0; }
104389
- .reader-body a { color:var(--sb-brand); }
104390
- .reader-body blockquote { margin-left:0; padding-left:16px; border-left:3px solid var(--sb-border-strong); }
104391
- .reader-body pre { overflow:auto; white-space:pre; }
104392
- .reader-body code { font-size:.9em; }
104393
- .table-scroll { max-width:100%; overflow:auto; }
104394
- .reader-body table { border-collapse:collapse; width:100%; }
104395
- .reader-body td, .reader-body th { text-align:left; vertical-align:top; padding:8px 10px; border:1px solid var(--sb-border-default); }
104396
- .reader-body th { background:var(--sb-bg-hover); }
104397
- #decision-summary ul { max-height:160px; overflow:auto; }
104398
- @media (max-width: 820px) {
104399
- .header { grid-template-columns:minmax(0, 1fr); }
104400
- .toolbar { justify-content:flex-start; flex-wrap:wrap; }
104401
- body { overflow:auto; }
104402
- .shell { height:auto; min-height:100vh; overflow:visible; padding:18px; }
104403
- .layout { grid-template-columns:1fr; }
104404
- .candidate-panel { max-height:42vh; }
104405
- .detail-panel { display:block; overflow:visible; }
104406
- .detail { overflow:visible; }
104407
- .detail-titlebar { display:grid; }
104193
+ // src/project/reviewSiteModel.ts
104194
+ var hash3 = (s) => createHash24("sha256").update(s).digest("hex");
104195
+ var body = (s) => s.replace(/^---\r?\n[\s\S]*?\r?\n---\s*/u, "").replace(/<!--[^]*?-->/gu, "").trim();
104196
+ var title = (s, fallback) => {
104197
+ const front = /^---\r?\n([\s\S]*?)\r?\n---/u.exec(s);
104198
+ const value = front ? import_yaml40.parse(front[1])?.title : undefined;
104199
+ return typeof value === "string" ? value : /^#\s+(.+)$/mu.exec(s)?.[1] ?? fallback;
104200
+ };
104201
+ async function optional2(path3) {
104202
+ try {
104203
+ return await readFile68(path3, "utf8");
104204
+ } catch (e) {
104205
+ if (e.code === "ENOENT")
104206
+ return;
104207
+ throw e;
104208
+ }
104209
+ }
104210
+ async function reviewSiteBaselineHash(root2, reviewedPaths) {
104211
+ const files = await readApprovedMarkdownFiles(root2);
104212
+ return hash3(JSON.stringify([
104213
+ await optional2(join86(root2, "src/knowledge-map.yaml")) ?? null,
104214
+ files.map((f) => [f.relPath, reviewedPaths.includes(f.relPath) ? hash3(f.content) : title(f.content, f.relPath)]).sort((a, b) => a[0].localeCompare(b[0]))
104215
+ ]));
104216
+ }
104217
+ function reviewBodyDiff(previous3, next) {
104218
+ function blocks(markdown) {
104219
+ const text9 = body(markdown);
104220
+ const tree = unified().use(remarkParse).use(remarkGfm).parse(text9);
104221
+ const definitions = tree.children.filter((node3) => node3.type === "definition").map((node3) => text9.slice(node3.position?.start.offset, node3.position?.end.offset)).join(`
104222
+ `);
104223
+ return tree.children.filter((node3) => node3.type !== "definition").map((node3) => renderReviewMarkdown(text9.slice(node3.position?.start.offset, node3.position?.end.offset) + `
104224
+
104225
+ ` + definitions));
104226
+ }
104227
+ const before = blocks(previous3), after = blocks(next);
104228
+ const remaining = [...before];
104229
+ let omitted = false;
104230
+ const result = after.map((block) => {
104231
+ const exact = remaining.indexOf(block);
104232
+ if (exact >= 0) {
104233
+ remaining.splice(exact, 1);
104234
+ if (/^<h[1-6]>/u.test(block)) {
104235
+ omitted = false;
104236
+ return block;
104237
+ }
104238
+ if (omitted)
104239
+ return "";
104240
+ omitted = true;
104241
+ return '<div class="unchanged" data-label="unchanged">Unchanged content omitted.</div>';
104242
+ }
104243
+ omitted = false;
104244
+ return `<section class="changed"><span class="badge modify">Modify</span>${block}</section>`;
104245
+ });
104246
+ if (remaining.length)
104247
+ result.push(`<section class="changed"><span class="badge modify">Modify</span><details open><summary data-label="removed">Previous or removed content</summary>${remaining.join(`
104248
+ `)}</details></section>`);
104249
+ return result.join(`
104250
+ `);
104251
+ }
104252
+ async function collectReviewSiteModel(root2, candidates) {
104253
+ const pendingFeedback = new Map((await readPendingReviewFeedback(root2, candidates)).map((r) => [r.candidate_id, r.instruction]));
104254
+ const current2 = await readKnowledgeMap(root2);
104255
+ const metadata = await readApprovedKnowledgeMetadataIndex(root2);
104256
+ const articles = metadata.structure?.articles ?? [];
104257
+ const files = await readApprovedMarkdownFiles(root2);
104258
+ const byPath = new Map(files.map((f) => [f.relPath, f.content]));
104259
+ const pages = files.map((f) => ({
104260
+ id: articles.find((a) => a.path === f.relPath)?.article_id ?? f.relPath,
104261
+ path: f.relPath,
104262
+ title: title(f.content, f.relPath),
104263
+ change: "unchanged",
104264
+ html: "",
104265
+ sources: []
104266
+ }));
104267
+ for (const { record: r } of candidates) {
104268
+ const found = pages.find((p) => p.id === r.article_id || p.path === (r.approved_revision?.previous_path ?? r.path));
104269
+ const old = found && byPath.get(found.path);
104270
+ const next = r.indexer_candidate.sections.map((s) => s.markdown).join(`
104271
+
104272
+ `);
104273
+ const page = {
104274
+ id: r.article_id,
104275
+ candidate_id: r.candidate_id,
104276
+ title: r.review.title,
104277
+ path: r.path,
104278
+ ...pendingFeedback.has(r.candidate_id) ? { revisionInstruction: pendingFeedback.get(r.candidate_id) } : {},
104279
+ ...found && found.path !== r.path ? { previousPath: found.path } : {},
104280
+ change: found ? "modify" : "new",
104281
+ html: old === undefined ? renderReviewMarkdown(next.replace(/^# [^\n]+\n*/u, "")) : reviewBodyDiff(body(old).replace(/^# [^\n]+\n*/u, ""), next.replace(/^# [^\n]+\n*/u, "")),
104282
+ sources: [...r.source_refs, ...r.indexer_candidate.sections.flatMap((s) => s.references.map((ref2) => JSON.stringify(ref2)))]
104283
+ };
104284
+ if (found)
104285
+ pages.splice(pages.indexOf(found), 1, page);
104286
+ else
104287
+ pages.push(page);
104288
+ }
104289
+ let baseline = current2, navigationBaseline = files.length ? "current" : "empty";
104290
+ if (files.length) {
104291
+ try {
104292
+ const { stdout } = await promisify10(execFile10)("git", ["show", "HEAD:./src/knowledge-map.yaml"], { cwd: root2, timeout: 5000, maxBuffer: 4 * 1024 * 1024 });
104293
+ baseline = validateKnowledgeMap(import_yaml40.parse(stdout));
104294
+ navigationBaseline = "git-head";
104295
+ } catch {}
104296
+ }
104297
+ const nodes = (current2?.entries ?? []).map((n) => {
104298
+ const old = baseline?.entries.find((b) => b.key === n.key);
104299
+ const page = n.target && pages.find((p) => p.id === n.target?.artifact_ref);
104300
+ return {
104301
+ key: n.key,
104302
+ parent: n.parent ?? null,
104303
+ title: n.title,
104304
+ order: n.order ?? 0,
104305
+ ...page ? { page: page.id } : {},
104306
+ ...old && old.title !== n.title ? { oldTitle: old.title } : {},
104307
+ change: navigationBaseline === "empty" || !old ? "new" : JSON.stringify(old) !== JSON.stringify(n) ? "modify" : "unchanged"
104308
+ };
104309
+ });
104310
+ for (const n of baseline?.entries ?? [])
104311
+ if (!nodes.some((v) => v.key === n.key))
104312
+ nodes.push({
104313
+ key: n.key,
104314
+ parent: n.parent ?? null,
104315
+ title: n.title,
104316
+ order: n.order ?? 0,
104317
+ removed: true,
104318
+ change: "modify"
104319
+ });
104320
+ const unplaced = pages.filter((p) => p.candidate_id && !nodes.some((n) => n.page === p.id));
104321
+ if (unplaced.length) {
104322
+ nodes.push({ key: "review-unplaced", parent: null, title: "Unplaced articles", order: Number.MAX_SAFE_INTEGER, change: "new" });
104323
+ for (const [i2, p] of unplaced.entries())
104324
+ nodes.push({ key: `review-page-${p.id}`, parent: "review-unplaced", title: p.title, order: i2, page: p.id, change: p.change });
104325
+ }
104326
+ const pkgText = await optional2(join86(root2, "package.json"));
104327
+ const pkg = pkgText ? JSON.parse(pkgText) : {};
104328
+ const project = await optional2(join86(root2, "src/index.ts")) === undefined ? undefined : await loadContextProjectModule(root2);
104329
+ const siteTitle = project?.project.packages.flatMap((p) => p.kind === "package.kb" && p.site?.title ? [p.site.title] : [])[0];
104330
+ return { title: siteTitle ?? pkg.name ?? "Knowledge review", baselineHash: await reviewSiteBaselineHash(root2, candidates.map((c) => c.record.approved_revision?.previous_path ?? c.record.path)), nodes, pages, navigationBaseline };
104331
+ }
104332
+ var reviewHtmlJson = (value) => JSON.stringify(value).replace(/</gu, "\\u003c").replace(/\u2028/gu, "\\u2028").replace(/\u2029/gu, "\\u2029");
104333
+
104334
+ // src/project/reviewFeedbackCode.ts
104335
+ function createReviewFeedbackCodec() {
104336
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
104337
+ function checksum(text9) {
104338
+ let crc = 4294967295;
104339
+ for (let i2 = 0;i2 < text9.length; i2++) {
104340
+ crc ^= text9.charCodeAt(i2);
104341
+ for (let j = 0;j < 8; j++)
104342
+ crc = crc >>> 1 ^ (crc & 1 ? 3988292384 : 0);
104343
+ }
104344
+ return ((crc ^ 4294967295) >>> 0).toString(16).padStart(8, "0");
104408
104345
  }
104346
+ function validate(value) {
104347
+ if (!value || !/^[a-z][a-z0-9-]*$/.test(value.scope) || ![value.idsHash, value.contentHash, value.baselineHash].every((h) => typeof h === "string" && /^[a-f0-9]{64}$/.test(h)) || !Array.isArray(value.statuses) || !value.statuses.length || value.statuses.length > 1e5 || value.statuses.some((s) => !["approved", "rejected", "pending", "revised"].includes(s)) || value.statuses.every((s) => s === "pending") || !Array.isArray(value.repairs))
104348
+ throw new Error("Invalid review feedback scope or decisions");
104349
+ const seen = new Set;
104350
+ for (const repair of value.repairs) {
104351
+ if (!repair || !Number.isInteger(repair.index) || seen.has(repair.index) || value.statuses[repair.index] !== "revised" || typeof repair.instruction !== "string" || !repair.instruction.trim() || repair.instruction.length > 20000)
104352
+ throw new Error("Invalid or conflicting revision instruction");
104353
+ seen.add(repair.index);
104354
+ }
104355
+ if (value.statuses.filter((s) => s === "revised").length !== seen.size)
104356
+ throw new Error("Missing revision instruction");
104357
+ return value;
104358
+ }
104359
+ function encode(value) {
104360
+ validate(value);
104361
+ const bytes = new TextEncoder().encode(JSON.stringify({ scope: value.scope, idsHash: value.idsHash, contentHash: value.contentHash, baselineHash: value.baselineHash, statuses: value.statuses }));
104362
+ let bits = 0, carry = 0, data2 = "";
104363
+ for (const byte of bytes) {
104364
+ carry = carry << 8 | byte;
104365
+ bits += 8;
104366
+ while (bits >= 6) {
104367
+ bits -= 6;
104368
+ data2 += alphabet[carry >>> bits & 63];
104369
+ }
104370
+ }
104371
+ if (bits)
104372
+ data2 += alphabet[carry << 6 - bits & 63];
104373
+ const body2 = `CR2.${data2}`;
104374
+ const repairs = value.repairs.map((r) => JSON.stringify([r.index, r.instruction])).join(`
104375
+ `);
104376
+ const trailer = repairs ? `
104377
+ ${repairs}` : "";
104378
+ const code3 = `${body2}.${checksum(body2 + trailer)}${trailer}`;
104379
+ if (code3.length > 250000)
104380
+ throw new Error("Review feedback is too large; shorten instructions or review a smaller scope");
104381
+ return code3;
104382
+ }
104383
+ function decode2(raw) {
104384
+ const code3 = raw.trim();
104385
+ if (code3.length > 250000)
104386
+ throw new Error("Review feedback exceeds supported size");
104387
+ const [header, ...lines] = code3.split(`
104388
+ `);
104389
+ const trailer = lines.length ? `
104390
+ ${lines.join(`
104391
+ `)}` : "";
104392
+ const match = /^CR2\.([A-Za-z0-9_-]+)\.([a-f0-9]{8})$/.exec(header);
104393
+ if (!match || checksum(`CR2.${match[1]}` + trailer) !== match[2])
104394
+ throw new Error("Review feedback is damaged or incomplete; copy the full code again");
104395
+ let bits = 0, carry = 0;
104396
+ const bytes = [];
104397
+ for (const char of match[1]) {
104398
+ carry = carry << 6 | alphabet.indexOf(char);
104399
+ bits += 6;
104400
+ if (bits >= 8) {
104401
+ bits -= 8;
104402
+ bytes.push(carry >>> bits & 255);
104403
+ }
104404
+ }
104405
+ const headerValue = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(new Uint8Array(bytes)));
104406
+ const value = validate({ ...headerValue, repairs: lines.map((line) => {
104407
+ const row = JSON.parse(line);
104408
+ if (!Array.isArray(row) || row.length !== 2)
104409
+ throw new Error("Invalid revision instruction line");
104410
+ return { index: row[0], instruction: row[1] };
104411
+ }) });
104412
+ if (encode(value) !== code3)
104413
+ throw new Error("Noncanonical review feedback encoding");
104414
+ return value;
104415
+ }
104416
+ return { encode, decode: decode2 };
104417
+ }
104418
+
104419
+ // src/project/reviewSiteClient.ts
104420
+ var REVIEW_SITE_CLIENT = String.raw`
104421
+ const $=id=>document.getElementById(id);
104422
+ let language=(navigator.languages?.[0]||navigator.language||'en').startsWith('zh')?'zh':'en';
104423
+ const words={newRoots:['新增一级目录','New top-level categories'],ackRoots:['我已知晓本次新增一级目录','I acknowledge the new top-level categories'],home:['待审核内容','Pages to review'],unchanged:['本次未变更内容略。','Unchanged content omitted.'],previous:['查看旧文本','Previous text'],removed:['删除的内容','Removed content'],unplaced:['待落位文章','Unplaced articles'],approve:['批准这篇','Approve page'],reject:['拒绝这篇','Reject page'],revise:['修订','Revise'],approved:['已批准','Approved'],rejected:['已拒绝','Rejected'],revised:['已修订','Revision requested'],cancel:['取消','Cancel'],copy:['复制审核码','Copy review code'],allApprove:['全部批准','Approve all'],allReject:['全部拒绝','Reject all'],note:['输入修订意见','Enter revision instructions'],guide:['逐篇阅读并批准或拒绝后,复制审核码回复给 Agent。需要修改的文章请填写修订意见。','Read each page, approve or reject, then copy the review code back to your Agent. Enter instructions for pages needing revision.'],known:['知道了','Got it'],close:['关闭','Close'],notReviewed:['尚未完成审核','Not yet reviewed'],confirmAll:['建议逐篇阅读并确认。除非已读完所有待审核文章,否则请勿一次性全部批准。已有拒绝和修订意见将保留。','Read and confirm each page. Approve all only after reading every pending page. Existing rejections and revisions are preserved.'],confirm:['已阅读,全部批准','Read all, approve'],copied:['审核码已复制','Review code copied'],failed:['复制失败,请手动复制下方完整内容','Copy failed. Copy the complete text below manually.'],instructions:['请回到和 Agent 的会话窗口粘贴已复制内容进行回复即可继续~','Return to your conversation with the Agent and reply with the copied content to continue.'],long:['超过 1000 字符,飞书表单可能不接受。飞书场景下建议 @Bot 后粘贴回复。','Over 1,000 characters: a Feishu form may reject it. Mention @Bot and paste it in a reply instead.'],files:['预期工作区变化','Expected workspace changes'],navigate:['目录与文章','Directories and articles'],pendingNew:['未审批的新增文章','Pending new pages'],pendingModify:['未审批的修改文章','Pending modified pages'],processed:['已经审核和修订的文章','Reviewed or revision requested'],noSelection:['请先选择审核结果或填写修订意见','Select a decision or enter revision instructions first'],baseline:['无 Git 导航基线,目录沿用当前工作区;未推断历史目录变化。','No Git navigation baseline. Current navigation is shown without inferred historic changes.'],placement:['待落位文章不是新的站点栏目;请先按既有分类完成导航规划。','Unplaced articles are not a new site category. Finish their placement in the existing navigation first.']};
104424
+ const t=k=>words[k]?.[language==='zh'?0:1]||k;
104425
+ const escape=s=>String(s).replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
104426
+ const decisions=new Map(),notes=new Map(),expanded=new Set();let selected=null,active=null;
104427
+ const candidates=DATA.pages.filter(p=>p.candidate_id),ordered=[...candidates].sort((a,b)=>a.candidate_id<b.candidate_id?-1:1);
104428
+ for(const p of candidates)if(p.revisionInstruction){decisions.set(p.candidate_id,'revised');notes.set(p.candidate_id,p.revisionInstruction)}
104429
+ const badge=change=>change==='unchanged'?'':'<span class="badge '+change+'">'+(change==='new'?'New':change==='modify'?'Modify':t(change))+'</span>';
104430
+ const state=p=>decisions.get(p.candidate_id)||'pending';
104431
+ function descendants(key,seen=new Set()){if(seen.has(key))return[];seen.add(key);return DATA.nodes.filter(n=>n.parent===key).flatMap(n=>[n,...descendants(n.key,seen)])}
104432
+ function nodeChange(n){const changes=[n.change,...[n,...descendants(n.key)].flatMap(v=>{const p=DATA.pages.find(p=>p.id===v.page);return p?[p.change]:[]}),...descendants(n.key).map(v=>v.change)];return changes.includes('modify')?'modify':changes.includes('new')?'new':'unchanged'}
104433
+ function nodeTitle(n){return n.key==='review-unplaced'?t('unplaced'):n.title}
104434
+ function nodeLabel(n){return (n.removed?'<del>':'')+escape(nodeTitle(n))+(n.removed?'</del>':'')+(n.oldTitle?'<small class="old-title"> ← '+escape(n.oldTitle)+'</small>':'')}
104435
+ function totals(){const result={new:0,modify:0,approved:0,rejected:0,revised:0,pending:0};for(const p of candidates){const s=state(p);result[s]++;if(s==='pending')result[p.change==='new'?'new':'modify']++}return result}
104436
+ function updateCounts(){const c=totals();$('counts').textContent=c.new+' New / '+c.modify+' Modify / '+(c.approved+c.rejected+c.revised)+' Confirm';$('counter-pop').innerHTML='<div class="counter-grid"><div><b>'+c.new+'</b>'+t('pendingNew')+'</div><div><b>'+c.modify+'</b>'+t('pendingModify')+'</div></div><p>'+t('processed')+': '+(c.approved+c.rejected+c.revised)+'</p><small>'+t('approved')+' '+c.approved+' · '+t('rejected')+' '+c.rejected+' · '+t('revised')+' '+c.revised+'</small>'}
104437
+ function button(n,depth){const change=nodeChange(n),page=DATA.pages.find(p=>p.id===n.page);return '<button data-node="'+escape(n.key)+'" class="node '+change+(selected===n.page?' selected':'')+'" style="padding-left:'+(16+14*depth)+'px">'+nodeLabel(n)+badge(change)+(page&&page.candidate_id&&state(page)!=='pending'?badge(state(page)):'')+(DATA.nodes.some(c=>c.parent===n.key)?'<span class="caret">›</span>':'')+'</button>'}
104438
+ function renderNav(){const roots=DATA.nodes.filter(n=>!n.parent).sort((a,b)=>a.order-b.order);$('top').innerHTML=roots.map(n=>'<button data-root="'+escape(n.key)+'" class="'+nodeChange(n)+(active===n.key?' active':'')+'">'+nodeLabel(n)+badge(nodeChange(n))+'</button>').join('');let html='';function walk(key,depth,seen=new Set()){if(seen.has(key))return;seen.add(key);for(const n of DATA.nodes.filter(n=>n.parent===key).sort((a,b)=>a.order-b.order)){html+=button(n,depth);if(expanded.has(n.key)||n.page===selected||descendants(n.key).some(d=>d.page===selected))walk(n.key,depth+1,seen)}}if(active)walk(active,0);else html=roots.map(n=>button(n,0)).join('');$('tree').innerHTML=html}
104439
+ function rootFor(page){let node=DATA.nodes.find(n=>n.page===page);const seen=new Set();while(node?.parent&&!seen.has(node.key)){seen.add(node.key);node=DATA.nodes.find(n=>n.key===node.parent)}return node?.key||null}
104440
+ function showPage(id){selected=id;active=rootFor(id)||active;render()}
104441
+ function localizeBody(){document.querySelectorAll('[data-label]').forEach(el=>{el.textContent=t(el.dataset.label)})}
104442
+ function workspaceTree(){const tree={};for(const p of candidates){let node=tree;for(const part of ('knowledge/'+p.path).split('/'))node=node[part]??=( {} );node.$page=p}function lines(node,level=0){return Object.entries(node).filter(([k])=>k!=='$page').sort(([a],[b])=>a.localeCompare(b)).map(([k,v])=>'<div style="padding-left:'+level*18+'px">'+(v.$page?'<button data-page="'+escape(v.$page.id)+'">'+escape(k)+'</button>'+badge(v.$page.change)+(state(v.$page)!=='pending'?badge(state(v.$page)):''):escape(k)+'/')+'</div>'+lines(v,level+1)).join('')}return '<div class="workspace-tree">'+lines(tree)+'</div>'}
104443
+ function renderHome(){const c=totals();$('article').innerHTML='<h1>'+t('home')+'</h1><p class="stats">'+candidates.length+' '+(language==='zh'?'篇候选正文':'candidate pages')+' · '+c.approved+' '+t('approved')+' '+badge('new')+' '+badge('modify')+'</p>'+(DATA.navigationBaseline==='current'?'<p class="note">'+t('baseline')+'</p>':'')+(DATA.nodes.some(n=>n.key==='review-unplaced')?'<p class="note">'+t('placement')+'</p>':'')+'<h2>'+t('navigate')+'</h2>'+DATA.nodes.filter(n=>!n.parent).map(n=>{const ids=new Set([n,...descendants(n.key)].map(x=>x.page));const pages=candidates.filter(p=>ids.has(p.id));return pages.length?'<section class="home-group"><h3>'+nodeLabel(n)+badge(nodeChange(n))+'</h3><div class="cards">'+pages.map(p=>'<button data-page="'+escape(p.id)+'">'+escape(p.title)+badge(p.change)+(state(p)!=='pending'?badge(state(p)):'')+'</button>').join('')+'</div></section>':''}).join('')+'<h2>'+t('files')+'</h2>'+workspaceTree()}
104444
+ function controls(){const p=DATA.pages.find(p=>p.id===selected),s=p?state(p):'pending';$('footer').hidden=!p?.candidate_id;if(!p?.candidate_id)return;$('revision-note').value=notes.get(p.candidate_id)||'';$('revision-note').placeholder=t('note');$('revision-note').disabled=s==='approved'||s==='rejected';for(const [id,v,label]of[['revise-btn','revised','revise'],['reject-btn','rejected','reject'],['approve-btn','approved','approve']]){const b=$(id);b.disabled=s!=='pending'&&s!==v;b.className='btn '+(s===v?'chosen':v==='approved'?'primary':'');b.innerHTML=s===v?'<span class="normal">'+t(v)+'</span><span class="hover-label">'+t('cancel')+'</span>':t(label);b.title=s===v?t('cancel'):''}}
104445
+ function render(){document.body.classList.toggle('home',selected===null);renderNav();updateCounts();if(selected===null)renderHome();else{const p=DATA.pages.find(p=>p.id===selected);$('article').innerHTML=p?'<h1>'+escape(p.title)+badge(p.change)+(p.candidate_id&&state(p)!=='pending'?badge(state(p)):'')+'</h1>'+(p.previousPath?'<p class="note">'+escape(p.previousPath)+' → '+escape(p.path)+'</p>':'')+(p.candidate_id?p.html:'<div class="unchanged">'+t('unchanged')+'</div>')+(p.sources.length?'<details><summary>'+(language==='zh'?'来源引用':'Sources')+'</summary><ul>'+p.sources.map(s=>'<li>'+escape(s)+'</li>').join('')+'</ul></details>':''):''}controls();localizeBody()}
104446
+ function setDecision(id,value){const current=decisions.get(id);if(current===value){decisions.delete(id);notes.delete(id)}else if(!current){if(value==='revised'){$('revision-note').focus();return}decisions.set(id,value)}render()}
104447
+ function setAllDecision(value){for(const p of candidates)if(!decisions.has(p.candidate_id))decisions.set(p.candidate_id,value);render()}
104448
+ function payloadText(){const statuses=ordered.map(p=>state(p));return feedbackCodec.encode({scope:SCOPE.label,idsHash:SCOPE.ids_sha256,contentHash:SCOPE.candidates_sha256,baselineHash:DATA.baselineHash,statuses,repairs:ordered.flatMap((p,index)=>state(p)==='revised'?[{index,instruction:notes.get(p.candidate_id)}]:[])})}
104449
+ async function copyPayload(){dismissGuide();const c=totals();$('copy-warning').textContent='';$('payload').value='';if(c.pending===candidates.length){$('copy-title').textContent=t('noSelection');$('payload').value=''}else{try{const text=payloadText();$('payload').value=text;await navigator.clipboard.writeText(text);$('copy-title').textContent=t('copied')}catch(e){$('copy-title').textContent=t('failed');if(!$('payload').value)$('payload').value=String(e.message)}}$('copy-summary').textContent=t('approved')+' '+c.approved+' · '+t('rejected')+' '+c.rejected+' · '+t('revised')+' '+c.revised+' · '+t('notReviewed')+' '+c.pending;$('copy-instructions').textContent=t('instructions');$('copy-warning').textContent=Array.from($('payload').value).length>1000?t('long'):'';$('copy-dialog').showModal()}
104450
+ let guideTimer;function dismissGuide(){clearInterval(guideTimer);$('copy-guide').hidden=true}
104451
+ function labels(){$('all-approved').textContent=t('allApprove');$('all-rejected').textContent=t('allReject');$('payload-open').textContent=t('copy');$('guide-text').textContent=t('guide');$('guide-close').textContent=t('known');$('bulk-title').textContent=t('allApprove');$('bulk-message').textContent=t('confirmAll');$('bulk-cancel').textContent=t('cancel');$('bulk-confirm').textContent=t('confirm');$('payload-close').textContent=t('close');$('language').textContent=language==='zh'?'EN':'中文'}
104452
+ document.addEventListener('click',e=>{const b=e.target.closest('button');if(!b)return;if(b.dataset.page){showPage(b.dataset.page);window.scrollTo(0,0)}if(b.dataset.root){active=b.dataset.root;const n=DATA.nodes.find(n=>n.key===active);selected=n?.page||[...descendants(active)].find(d=>d.page)?.page||null;render()}if(b.dataset.node){const n=DATA.nodes.find(n=>n.key===b.dataset.node);if(n.page)showPage(n.page);else{expanded.has(n.key)?expanded.delete(n.key):expanded.add(n.key);renderNav()}}});
104453
+ $('home').onclick=()=>{selected=null;active=null;render();window.scrollTo(0,0)};
104454
+ $('revision-note').oninput=e=>{const p=DATA.pages.find(p=>p.id===selected);if(!p?.candidate_id)return;const value=e.target.value;notes.set(p.candidate_id,value);if(value.trim())decisions.set(p.candidate_id,'revised');else decisions.delete(p.candidate_id);controls();renderNav();updateCounts();$('article').querySelectorAll('h1 > .approved,h1 > .revised,h1 > .rejected').forEach(e=>e.remove());$('article').querySelector('h1')?.insertAdjacentHTML('beforeend',value.trim()?badge('revised'):'')};
104455
+ for(const [id,value]of[['revise-btn','revised'],['reject-btn','rejected'],['approve-btn','approved']])$(id).onclick=()=>{const p=DATA.pages.find(p=>p.id===selected);if(p?.candidate_id)setDecision(p.candidate_id,value)};
104456
+ const addedRoots=DATA.nodes.filter(n=>!n.parent&&n.change==='new'&&!n.removed&&n.key!=='review-unplaced');
104457
+ let bulkTimer,bulkDeadline=0;
104458
+ function updateBulkConfirmation(){const remaining=addedRoots.length?Math.max(0,Math.ceil((bulkDeadline-Date.now())/1000)):0;$('bulk-confirm').disabled=addedRoots.length>0&&(!$('bulk-ack').checked||remaining>0);$('bulk-confirm').textContent=t('confirm')+(remaining?' ('+remaining+'s)':'');return remaining}
104459
+ function openBulkConfirmation(){clearInterval(bulkTimer);$('bulk-ack').checked=false;$('bulk-roots').hidden=!addedRoots.length;$('bulk-roots-title').textContent=t('newRoots');$('bulk-roots-list').innerHTML=addedRoots.map(n=>'<li>'+escape(nodeTitle(n))+'</li>').join('');$('bulk-ack-label').textContent=t('ackRoots');bulkDeadline=Date.now()+8000;updateBulkConfirmation();$('bulk-dialog').showModal();if(addedRoots.length)bulkTimer=setInterval(()=>{if(!updateBulkConfirmation())clearInterval(bulkTimer)},200)}
104460
+ $('bulk-ack').onchange=updateBulkConfirmation;
104461
+ $('bulk-dialog').onclose=()=>clearInterval(bulkTimer);
104462
+ $('all-approved').onclick=openBulkConfirmation;$('bulk-confirm').onclick=()=>{updateBulkConfirmation();if(!$('bulk-dialog').open||$('bulk-confirm').disabled)return;setAllDecision('approved');clearInterval(bulkTimer);$('bulk-dialog').close()};$('bulk-cancel').onclick=()=>{clearInterval(bulkTimer);$('bulk-dialog').close()};$('all-rejected').onclick=()=>setAllDecision('rejected');$('payload-open').onclick=copyPayload;$('payload-close').onclick=()=>$('copy-dialog').close();$('guide-close').onclick=dismissGuide;$('theme').onclick=()=>document.body.classList.toggle('dark');$('language').onclick=()=>{language=language==='zh'?'en':'zh';labels();render()};
104463
+ labels();render();const deadline=Date.now()+10000;guideTimer=setInterval(()=>{const left=Math.max(0,Math.ceil((deadline-Date.now())/1000));$('guide-countdown').textContent=left+'s';if(!left)dismissGuide()},250);
104464
+ `;
104465
+
104466
+ // src/project/reviewSiteStyles.ts
104467
+ var REVIEW_SITE_STYLES = String.raw`
104468
+ :root{--blue:#2563eb;--text:#161e2e;--muted:#646b7c;--line:#e7e9ef;--bg:#fff;--side:#f8f9fc;--red:#c63848;--amber:#a4660a}*{box-sizing:border-box}body{margin:0;color:var(--text);background:var(--bg);font:14px/1.75 -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC",sans-serif}button,input,textarea{font:inherit}button{cursor:pointer}button:disabled{opacity:.35;cursor:not-allowed}[hidden]{display:none!important}header{height:64px;padding:0 24px;display:flex;align-items:center;border-bottom:1px solid var(--line);position:sticky;top:0;background:var(--bg);z-index:5}#home{border:0;background:none;color:var(--text);width:280px;flex-shrink:0;text-align:left;font-size:15px;font-weight:650;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}nav{display:flex;flex:1;min-width:0;overflow:auto;align-self:stretch}nav button{border:0;background:none;color:var(--text);padding:0 16px;font-size:13px;font-weight:550;white-space:nowrap}nav .active{box-shadow:inset 0 -2px var(--blue)}.new,nav .new{color:var(--red)}.modify,nav .modify{color:var(--amber)}.badge{display:inline-block;margin-left:7px;padding:1px 6px;font-size:10px;line-height:18px;vertical-align:middle;border-radius:4px;font-weight:600;letter-spacing:0}.badge.new{background:#fff0f1}.badge.modify,.badge.revised{background:#fff4dd;color:var(--amber)}.badge.approved{background:#e9f7ef;color:#258451}.badge.rejected{background:#f4edf0;color:#9e4458}.tools{display:flex;align-items:center;gap:8px;position:relative;margin-left:12px}.btn{border:1px solid var(--line);border-radius:6px;background:var(--bg);color:var(--text);padding:5px 10px;font-size:12px;line-height:22px;white-space:nowrap}.primary{color:white;background:var(--blue);border-color:var(--blue)}#theme,#language{margin-left:8px;flex-shrink:0}.counter{position:relative;padding:8px;white-space:nowrap;font-size:12px;font-weight:700;color:var(--blue)}.counter-pop{display:none;position:absolute;top:100%;right:0;min-width:320px;padding:15px;background:var(--bg);border:1px solid var(--line);border-radius:10px;box-shadow:0 8px 30px #17244220;color:var(--text);font-weight:400}.counter:hover .counter-pop,.counter:focus .counter-pop{display:block}.counter-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px;font-size:11px}.counter-grid b{display:block;font-size:20px}.counter-pop p{border-top:1px solid var(--line);padding-top:10px}.layout{display:grid;grid-template-columns:280px minmax(0,1fr)}aside{height:calc(100vh - 64px);position:sticky;top:64px;overflow:auto;background:var(--side);border-right:1px solid var(--line);padding:12px 0 80px}.node{display:block;width:100%;text-align:left;border:0;background:none;min-height:32px;padding:6px 16px;font-size:13px;font-weight:450;line-height:20px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--muted)}.node.new{color:var(--red)}.node.modify{color:var(--amber)}.node.selected{background:#2563eb14}.caret{float:right}.node:hover{background:#8e96aa1a}main{min-width:0;padding:44px 60px 100px 36px}h1{font-size:36px;line-height:1.3;letter-spacing:-.025em;margin:0 0 28px}h2{font-size:25px;line-height:1.4;font-weight:650;margin:46px 0 22px;border-bottom:1px solid var(--line);padding-bottom:14px}h3{font-size:19px;margin:30px 0 14px}article{font-size:16px;line-height:1.8}article>h1>.badge{margin-left:12px}article a{color:inherit}article a:hover{color:var(--blue)}article table{border-collapse:collapse;font-size:14px;display:block;overflow:auto}article th,article td{border:1px solid var(--line);padding:11px 14px}article th{background:var(--side)}pre{overflow:auto;background:var(--side);padding:16px}blockquote{padding:16px 20px;margin:24px 0;border-left:3px solid #00bec8;background:#2563eb14;color:var(--muted)}.home .layout{display:block}.home aside{display:none}.home main{width:1120px;max-width:calc(100% - 280px);margin-left:280px;padding-top:32px}.home article{font-size:14px}.home h1{font-size:28px;margin-bottom:12px}.home h2{font-size:19px;margin:24px 0 12px;padding-bottom:10px}.home h3{font-size:15px;margin:16px 0 10px}.stats{font-size:12px;color:var(--muted)}.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(270px,1fr));gap:8px}.cards button{border:1px solid var(--line);border-radius:7px;background:var(--bg);color:var(--text);text-align:left;font-size:13px;line-height:20px;padding:10px 14px}.cards button:hover{border-color:var(--blue)}.unchanged{border:1px dashed var(--line);border-radius:8px;padding:24px;text-align:center;color:var(--muted);font-size:13px}.changed{position:relative;border-left:3px solid #e8b051;background:#fffaf0;color:#8f5607;padding:24px 18px 12px;margin:20px 0}.changed>.badge{position:absolute;right:12px;top:6px}.changed details{color:var(--muted)}.workspace-tree{font:12px/1.9 ui-monospace,monospace;border:1px solid var(--line);border-radius:8px;padding:16px;background:var(--side);overflow:auto}.workspace-tree button{border:0;background:none;color:var(--text);padding:0;font:inherit;white-space:nowrap}.note{font-size:12px;color:var(--muted)}footer{position:fixed;bottom:0;left:280px;right:0;background:var(--bg);border-top:1px solid var(--line);padding:12px 35px;display:flex;gap:10px;z-index:4}footer input{flex:1;min-width:0;border:0;outline:0;background:transparent;color:var(--text);font-size:14px;padding:8px 0}footer .btn{min-width:76px}.chosen{color:var(--blue);border-color:var(--blue);background:var(--bg)}.hover-label{display:none}.chosen:hover .normal{display:none}.chosen:hover .hover-label{display:inline}dialog{width:560px;max-width:90vw;background:var(--bg);color:var(--text);border:1px solid var(--line);border-radius:10px;padding:20px;font-size:13px;line-height:1.65}dialog::backdrop{background:#131b3255}dialog h2{font-size:17px;margin:0 0 12px;border:0;padding:0}dialog textarea{width:100%;height:130px;border:1px solid var(--line);border-radius:6px;padding:10px;font:11px/1.6 monospace;background:var(--side);color:var(--text)}#copy-warning{color:var(--amber)}.bulk-ack{display:flex;align-items:center;gap:8px;font-size:13px}.bulk-ack input{accent-color:var(--blue)}#bulk-roots-list{margin:8px 0 12px;padding-left:22px;color:var(--red)}.dialog-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:18px}.guide{position:absolute;right:0;top:calc(100% + 18px);width:300px;background:var(--bg);border:1px solid var(--line);border-radius:10px;padding:15px;box-shadow:0 10px 35px #17244224;font-size:12px}.guide:before{content:'';position:absolute;top:-7px;right:42px;width:12px;height:12px;background:var(--bg);border-top:1px solid var(--line);border-left:1px solid var(--line);transform:rotate(45deg)}#guide-countdown{float:right;color:var(--muted);font-size:11px}.dark{--bg:#171b24;--side:#1b1d24;--text:#e2e4ed;--muted:#a1a6b7;--line:#303440;--blue:#8eb7ff}.dark .changed{background:#302919;color:#f2cf85}@media(min-width:1600px){main,.home main{padding-left:52px}}@media(max-width:1250px){#home{width:190px}nav button{padding:0 8px}.tools{gap:4px}}@media(max-width:1050px){header{height:auto;flex-wrap:wrap;min-height:64px}nav{order:3;flex-basis:100%;height:44px}.tools{margin-left:auto}}@media(max-width:700px){.layout{display:block}aside{position:relative;top:0;height:200px}main,.home main{width:100%;max-width:100%;margin:0;padding:24px 20px 90px}footer{left:0;padding:10px;flex-wrap:wrap}footer input{flex-basis:100%}.tools{flex-wrap:wrap}h1{font-size:28px}}
104409
104469
  `;
104410
104470
 
104411
104471
  // src/project/reviewHtml.ts
104412
- var REVIEW_HTML_ROOT = join85(".tmp", "context-runtime", "review");
104472
+ var REVIEW_HTML_ROOT = join87(".tmp", "context-runtime", "review");
104413
104473
  async function collectReviewCandidates(projectRoot, collection) {
104414
104474
  const rows = await readCandidateRecords(projectRoot);
104415
104475
  const draftRows = rows.filter((row) => row.collection === collection && row.status === "draft");
@@ -104426,446 +104486,23 @@ async function collectAllReviewCandidates(projectRoot) {
104426
104486
  snapshot: await readReviewCandidateSnapshot(projectRoot, record4)
104427
104487
  })));
104428
104488
  }
104429
- function escapeHtml(value) {
104430
- return value.replace(/&/gu, "&amp;").replace(/</gu, "&lt;").replace(/>/gu, "&gt;").replace(/"/gu, "&quot;");
104431
- }
104432
- function jsonForScript(value) {
104433
- return JSON.stringify(value).replace(/</gu, "\\u003c").replace(/>/gu, "\\u003e").replace(/&/gu, "\\u0026").replace(/\u2028/gu, "\\u2028").replace(/\u2029/gu, "\\u2029");
104434
- }
104435
- function renderReviewHtml(candidates, reviewScope) {
104436
- const candidateIds = candidates.map(({ record: record4 }) => record4.candidate_id);
104437
- const visibleCandidateIds = [...candidateIds].sort();
104489
+ function renderReviewHtml(candidates, reviewScope, model) {
104438
104490
  const scope2 = {
104439
- kind: reviewScope === "all" ? "all" : "collection",
104440
- ...reviewScope !== "all" ? { collection: reviewScope } : {},
104441
- count: visibleCandidateIds.length,
104442
- ids_sha256: candidateIdsHash(visibleCandidateIds),
104443
- candidates_sha256: candidateSetHash(candidates.map(({ record: record4 }) => record4))
104444
- };
104445
- const candidateData = candidates.map(({ record: record4, snapshot }) => {
104446
- return {
104447
- candidate_id: record4.candidate_id,
104448
- collection: record4.collection,
104449
- path: record4.path,
104450
- article_id: record4.article_id,
104451
- module: record4.module,
104452
- status: record4.status,
104453
- kind: record4.kind,
104454
- visibility: record4.visibility,
104455
- source_refs: record4.source_refs,
104456
- source_paths: record4.indexer_candidate === undefined ? [] : [...new Set(record4.indexer_candidate.sections.flatMap((section) => section.references).map((binding) => binding.locator.path))].sort(),
104457
- sections: record4.indexer_candidate.sections.map((section) => ({
104458
- id: section.section_key,
104459
- kind: record4.kind,
104460
- summary: section.section_key,
104461
- body: section.markdown,
104462
- source_refs: [...new Set(section.references.map((reference2) => `${reference2.source_ref}/${reference2.locator.path}#L${reference2.locator.start_line}-L${reference2.locator.end_line}`))].sort(),
104463
- content_mode: "authored"
104464
- })),
104465
- group_key: reviewScope === "all" ? `${record4.collection} / ${candidateGroupKey({ record: record4, snapshot })}` : candidateGroupKey({ record: record4, snapshot }),
104466
- group_label: reviewScope === "all" ? `${record4.collection} · ${candidateGroupLabel({ record: record4, snapshot })}` : candidateGroupLabel({ record: record4, snapshot }),
104467
- review: record4.review,
104468
- display_summary: record4.review.behavior_summary ?? record4.review.summary,
104469
- preview: candidatePreview({ record: record4, snapshot }),
104470
- rendered_markdown: renderReviewMarkdown(record4.indexer_candidate.sections.map((section) => section.markdown).join(`
104471
-
104472
- `), record4.review.title),
104473
- snapshot_ready: snapshot !== undefined
104474
- };
104475
- });
104476
- return `<!doctype html>
104477
- <html lang="en" data-theme="light">
104478
- <head>
104479
- <meta charset="utf-8">
104480
- <meta name="viewport" content="width=device-width, initial-scale=1">
104481
- <title>Context Review - ${escapeHtml(reviewScope)}</title>
104482
- <style>${REVIEW_HTML_STYLES}</style>
104483
- </head>
104484
- <body>
104485
- <main class="shell">
104486
- <header class="header">
104487
- <div class="titleline">
104488
- <h1 id="review-heading">Context Review</h1>
104489
- <div class="subtle" id="count-state">${candidates.length} draft candidate(s) in ${escapeHtml(reviewScope)} · ${candidates.length} pending 0 approved 0 omitted</div>
104490
- </div>
104491
- <div class="toolbar">
104492
- <span class="bulk-actions">
104493
- <button class="btn" id="all-approved">All approved</button>
104494
- <button class="btn" id="all-rejected">Omit all</button>
104495
- </span>
104496
- <button class="btn brand" id="payload-open">Copy review results</button>
104497
- <button class="btn language-btn" id="language" type="button" aria-label="Switch to Chinese">中文</button>
104498
- <button class="btn icon-btn" id="theme" title="Toggle theme" aria-label="Toggle theme">\uD83C\uDF19</button>
104499
- </div>
104500
- </header>
104501
- <section class="layout">
104502
- <aside class="panel candidate-panel">
104503
- <div class="panel-head candidate-head">
104504
- <span id="pages-label">Pages to review</span>
104505
- <div class="filters" id="filters" aria-label="candidate filters">
104506
- <label class="filter"><input id="filter-approved" type="checkbox" checked> <span id="label-approved">approved</span></label>
104507
- <label class="filter"><input id="filter-rejected" type="checkbox" checked> <span id="label-rejected">omitted</span></label>
104508
- <label class="filter"><input id="filter-pending" type="checkbox" checked> <span id="label-pending">pending</span></label>
104509
- </div>
104510
- </div>
104511
- <input id="search" type="search" placeholder="Search pages or modules" aria-label="Search pages or modules">
104512
- <div id="list"></div>
104513
- </aside>
104514
- <section class="panel detail-panel">
104515
- <div class="panel-head reader-navigation"><span id="content-label">Page content</span><div><button class="btn" id="previous-page">Previous</button> <button class="btn" id="next-page">Next</button> <button class="btn" id="next-pending">Next pending</button></div></div>
104516
- <div class="detail" id="detail"></div>
104517
- </section>
104518
- </section>
104519
- <div class="modal hidden" id="payload-modal" role="dialog" aria-modal="true" aria-labelledby="payload-title">
104520
- <section class="modal-card">
104521
- <div>
104522
- <h2 id="payload-title">Review results</h2>
104523
- <div class="subtle" id="code-help">These choices take effect only after you send the review code back to the conversation. Each segment is at most 980 characters. Send every segment before applying.</div>
104524
- </div>
104525
- <div class="modal-body">
104526
- <div id="decision-summary"></div>
104527
- <div class="code-navigation" id="code-navigation" hidden><button class="btn" id="code-previous">Previous segment</button><span id="code-length"></span><button class="btn" id="code-next">Next segment</button></div>
104528
- <textarea id="payload" aria-label="review code" readonly></textarea>
104529
- </div>
104530
- <div class="modal-actions">
104531
- <span class="subtle" id="modal-copy-state"></span>
104532
- <button class="btn" id="payload-close">Close</button>
104533
- <button class="btn primary" id="payload-copy">Copy</button>
104534
- </div>
104535
- </section>
104536
- </div>
104537
- </main>
104538
- <script>
104539
- const translations = ${jsonForScript(REVIEW_UI_ZH)};
104540
- let language = /^zh(?:-|$)/i.test(navigator.languages?.[0] || navigator.language || "en") ? "zh-CN" : "en";
104541
- function t(message, values = {}) {
104542
- const text = language === "zh-CN" ? translations[message] || message : message;
104543
- return text.replace(/\\{(\\w+)\\}/g, (_, key) => String(values[key] ?? ""));
104544
- }
104545
- const candidates = ${jsonForScript(candidateData)};
104546
- const payloadScope = ${jsonForScript(scope2)};
104547
- const reviewCode = (${createReviewCodeCodec.toString()})();
104548
- const payloadScopeLabel = ${jsonForScript(reviewScope)};
104549
- const decisions = new Map(candidates.map((item) => [item.candidate_id, "pending"]));
104550
- const list = document.getElementById("list");
104551
- const detail = document.getElementById("detail");
104552
- const countState = document.getElementById("count-state");
104553
- const filterApproved = document.getElementById("filter-approved");
104554
- const filterRejected = document.getElementById("filter-rejected");
104555
- const filterPending = document.getElementById("filter-pending");
104556
- const theme = document.getElementById("theme");
104557
- const allApproved = document.getElementById("all-approved");
104558
- const allRejected = document.getElementById("all-rejected");
104559
- const payloadOpen = document.getElementById("payload-open");
104560
- const payloadModal = document.getElementById("payload-modal");
104561
- const payloadClose = document.getElementById("payload-close");
104562
- const payloadCopy = document.getElementById("payload-copy");
104563
- const payloadBox = document.getElementById("payload");
104564
- const modalCopyState = document.getElementById("modal-copy-state");
104565
- const search = document.getElementById("search");
104566
- const decisionSummary = document.getElementById("decision-summary");
104567
- const codeLength = document.getElementById("code-length");
104568
- let codePart = 0;
104569
- let selected = candidates[0]?.candidate_id;
104570
- const collapsedGroups = new Set();
104571
-
104572
- function html(value) {
104573
- return String(value).replace(/[&<>"]/g, (char) => ({ "&":"&amp;", "<":"&lt;", ">":"&gt;", '"':"&quot;" }[char]));
104574
- }
104575
- function decisionCounts() {
104576
- const counts = { pending: 0, approved: 0, rejected: 0 };
104577
- for (const status of decisions.values()) counts[status] += 1;
104578
- return counts;
104579
- }
104580
- function updateCountState() {
104581
- const counts = decisionCounts();
104582
- countState.textContent = t("{count} pages · {scope} · {pending} pending · {approved} approved · {rejected} omitted",
104583
- { count: candidates.length, scope: payloadScopeLabel === "all" ? t("All collections") : payloadScopeLabel, ...counts });
104584
- }
104585
- function visibleCandidates() {
104586
- const showApproved = filterApproved.checked;
104587
- const showRejected = filterRejected.checked;
104588
- const showPending = filterPending.checked;
104589
- const query = search.value.trim().toLowerCase();
104590
- return candidates.filter((item) => {
104591
- if (query && !(item.review.title + " " + item.module + " " + item.source_paths.join(" ")).toLowerCase().includes(query)) return false;
104592
- const status = decisions.get(item.candidate_id);
104593
- return (status === "pending" && showPending) ||
104594
- (status === "approved" && showApproved) ||
104595
- (status === "rejected" && showRejected);
104596
- });
104597
- }
104598
- function groupCandidates(items) {
104599
- const groups = [];
104600
- const byGroup = new Map();
104601
- for (const item of items) {
104602
- const key = item.group_key || item.module || "ungrouped";
104603
- let group = byGroup.get(key);
104604
- if (!group) {
104605
- group = { key, label: item.group_label || key, items: [] };
104606
- byGroup.set(key, group);
104607
- groups.push(group);
104608
- }
104609
- group.items.push(item);
104610
- }
104611
- return groups;
104612
- }
104613
- function statusBadge(status) {
104614
- const label = t(status === "rejected" ? "omitted" : status);
104615
- return '<span class="badge ' + html(status) + '">' + html(label) + '</span>';
104616
- }
104617
- function toggleGroup(groupKey) {
104618
- if (collapsedGroups.has(groupKey)) collapsedGroups.delete(groupKey);
104619
- else collapsedGroups.add(groupKey);
104620
- render();
104621
- }
104622
- function setGroupDecision(groupKey, status) {
104623
- const items = candidates.filter((item) => (item.group_key || item.module || "ungrouped") === groupKey);
104624
- if (items.length === 0) return;
104625
- const label = t(status === "rejected" ? "omitted" : status);
104626
- if (!window.confirm(t("Set all {count} pages in {group} to {status}?", { count: items.length, group: groupKey, status: label }))) return;
104627
- for (const item of items) {
104628
- if (status === "approved" && !item.snapshot_ready) continue;
104629
- decisions.set(item.candidate_id, status);
104630
- }
104631
- codePart = 0;
104632
- modalCopyState.textContent = t("Choices changed. Copy the updated code before applying.");
104633
- render();
104634
- updatePayloadBox();
104635
- }
104636
- function setAllDecision(status) {
104637
- if (candidates.length === 0) return;
104638
- const label = t(status === "rejected" ? "omitted" : status);
104639
- if (!window.confirm(t("Set all {count} pages to {status}?", { count: candidates.length, status: label }))) return;
104640
- for (const item of candidates) {
104641
- if (status === "approved" && !item.snapshot_ready) continue;
104642
- decisions.set(item.candidate_id, status);
104643
- }
104644
- codePart = 0;
104645
- modalCopyState.textContent = t("Choices changed. Copy the updated code before applying.");
104646
- render();
104647
- updatePayloadBox();
104648
- }
104649
- function payloadParts() {
104650
- if (decisionCounts().pending === candidates.length) return [];
104651
- const ordered = [...candidates].sort((a, b) => a.candidate_id < b.candidate_id ? -1 : a.candidate_id > b.candidate_id ? 1 : 0);
104652
- return reviewCode.encode(payloadScopeLabel, payloadScope.ids_sha256, payloadScope.candidates_sha256,
104653
- ordered.map((item) => decisions.get(item.candidate_id)));
104654
- }
104655
- function payloadText() { return payloadParts()[codePart] || t("Select at least one page decision; pending pages remain for later review."); }
104656
- function setDecision(id, status) {
104657
- const item = candidates.find((candidate) => candidate.candidate_id === id);
104658
- if (status === "approved" && item && !item.snapshot_ready) return;
104659
- decisions.set(id, status);
104660
- codePart = 0;
104661
- modalCopyState.textContent = t("Choices changed. Copy the updated code before applying.");
104662
- render();
104663
- updatePayloadBox();
104664
- }
104665
- function updatePayloadBox() {
104666
- const parts = payloadParts();
104667
- codePart = Math.min(codePart, Math.max(0, parts.length - 1));
104668
- payloadBox.value = payloadText();
104669
- document.getElementById("code-navigation").hidden = parts.length <= 1;
104670
- codeLength.textContent = parts.length ? t("Segment {part}/{total} · {length}/980 characters", { part: codePart + 1, total: parts.length, length: payloadBox.value.length }) : t("No review code yet");
104671
- document.getElementById("code-previous").disabled = codePart === 0;
104672
- document.getElementById("code-next").disabled = codePart + 1 >= parts.length;
104673
- const counts = decisionCounts();
104674
- const ready = counts.approved + counts.rejected > 0;
104675
- decisionSummary.innerHTML = '<p>' + html(t('{approved} approved · {rejected} not included · {pending} pending', counts)) + '</p>' +
104676
- (counts.rejected ? '<details><summary>' + html(t('Pages not included')) + '</summary><ul>' + candidates.filter((item) => decisions.get(item.candidate_id) === "rejected").map((item) => '<li>' + html(item.review.title) + '</li>').join('') + '</ul></details>' : '');
104677
- payloadCopy.disabled = !ready;
104678
- payloadOpen.classList.toggle("ready", ready);
104679
- payloadOpen.title = ready
104680
- ? t("Open review results")
104681
- : t("{count} pending pages remain", { count: counts.pending });
104682
- }
104683
- function openPayloadModal() {
104684
- updatePayloadBox();
104685
- payloadModal.classList.remove("hidden");
104686
- payloadBox.focus();
104687
- payloadBox.select();
104688
- modalCopyState.textContent = "";
104689
- }
104690
- function closePayloadModal() {
104691
- payloadModal.classList.add("hidden");
104692
- }
104693
- async function copyPayload() {
104694
- const counts = decisionCounts();
104695
- if (counts.approved + counts.rejected === 0) {
104696
- updatePayloadBox();
104697
- const message = t("Select at least one page decision; pending pages remain for later review.");
104698
- modalCopyState.textContent = message;
104699
- return;
104700
- }
104701
- const text = payloadText();
104702
- payloadBox.value = text;
104703
- try {
104704
- if (!navigator.clipboard) throw new Error("clipboard unavailable");
104705
- await navigator.clipboard.writeText(text);
104706
- modalCopyState.textContent = t("Copied");
104707
- } catch {
104708
- payloadBox.focus();
104709
- payloadBox.select();
104710
- modalCopyState.textContent = t("Copy manually from the textarea");
104711
- }
104712
- }
104713
- function render() {
104714
- updateCountState();
104715
- if (candidates.length === 0) {
104716
- list.innerHTML = '<div class="empty">' + html(t('No draft candidates.')) + '</div>';
104717
- detail.innerHTML = '<div class="empty">' + html(t('Nothing to review.')) + '</div>';
104718
- return;
104719
- }
104720
- const visible = visibleCandidates();
104721
- if (visible.length === 0) {
104722
- list.innerHTML = '<div class="empty">' + html(t('No candidates match the current filters.')) + '</div>';
104723
- detail.innerHTML = '<div class="empty">' + html(t('Adjust the candidate filters to continue reviewing.')) + '</div>';
104724
- return;
104725
- }
104726
- if (!visible.some((item) => item.candidate_id === selected)) selected = visible[0].candidate_id;
104727
- list.innerHTML = groupCandidates(visible).map((group) =>
104728
- {
104729
- const collapsed = collapsedGroups.has(group.key);
104730
- return '<section class="candidate-group">' +
104731
- '<div class="candidate-group-title" data-group-toggle="' + html(group.key) + '">' +
104732
- '<span class="group-label"><span>' + (collapsed ? "▸" : "▾") + '</span><span class="group-key">' + html(group.label) + '</span><span class="group-count">' + group.items.length + ' items</span></span>' +
104733
- '<span class="group-actions">' +
104734
- '<button class="group-btn" data-group-status="approved" data-group="' + html(group.key) + '">' + html(t('All approved')) + '</button>' +
104735
- '<button class="group-btn" data-group-status="rejected" data-group="' + html(group.key) + '">' + html(t('Omit all')) + '</button>' +
104736
- '</span>' +
104737
- '</div>' +
104738
- (collapsed ? "" : group.items.map((item) => {
104739
- const active = item.candidate_id === selected ? " active" : "";
104740
- const status = decisions.get(item.candidate_id);
104741
- return '<button class="candidate' + active + '" data-id="' + html(item.candidate_id) + '">' +
104742
- '<div class="candidate-title">' +
104743
- '<span class="candidate-title-text">' + html(item.review.title) + '</span>' +
104744
- '<span class="candidate-tags"><span class="badge">' + html(item.collection || "unknown") + '</span>' + (!item.snapshot_ready ? '<span class="badge warning">' + html(t('evidence unavailable')) + '</span>' : '') + statusBadge(status) + '</span>' +
104745
- '</div>' +
104746
- '<div class="candidate-summary">' + html(item.display_summary || item.review.summary) + '</div>' +
104747
- '</button>';
104748
- }).join("")) +
104749
- '</section>';
104750
- }
104751
- ).join("");
104752
- const item = visible.find((candidate) => candidate.candidate_id === selected) ?? visible[0];
104753
- selected = item.candidate_id;
104754
- const status = decisions.get(item.candidate_id);
104755
- const evidenceWarning = item.snapshot_ready ? "" :
104756
- '<div class="notice warning">' + html(t('Source snapshot unavailable. Restore it before approving this candidate, or omit the page.')) + '</div>';
104757
- const sectionDetails = '<article class="reader-body">' + item.rendered_markdown + '</article>';
104758
- const previewBlock = '<details class="technical-details"><summary>' + html(t('Source Markdown')) + '</summary><pre>' +
104759
- html(item.sections.map((section) => section.body).join("\\n\\n")) + '</pre></details>';
104760
- const displayedSources = [...new Set([...item.source_paths, ...item.source_refs, ...item.sections.flatMap((section) => section.source_refs)])];
104761
- const sourceLocationsBlock = displayedSources.length === 0 ? "" :
104762
- '<details class="technical-details">' +
104763
- '<summary>' + html(t('Source locations')) + '(' + displayedSources.length + ')</summary>' +
104764
- '<div class="technical-content"><div class="section-source-refs">' +
104765
- displayedSources.map((ref) => '<code>' + html(ref) + '</code>').join('') +
104766
- '</div></div>' +
104767
- '</details>';
104768
- detail.innerHTML = '<div class="detail-titlebar">' +
104769
- '<div class="page-location">' + html(item.path) + '</div>' +
104770
- '<div class="actions">' +
104771
- '<button class="btn approve ' + (status === "approved" ? "active" : "") + '" data-action="approved" ' + (!item.snapshot_ready ? "disabled" : "") + '>' + html(t('Approve')) + '</button>' +
104772
- '<button class="btn reject ' + (status === "rejected" ? "active" : "") + '" data-action="rejected">' + html(t('Omit')) + '</button>' +
104773
- '</div>' +
104774
- '</div>' +
104775
- evidenceWarning +
104776
- sectionDetails +
104777
- previewBlock +
104778
- '<p class="repair-hint">' + html(t('Need changes? Leave this page pending and ask the agent to repair it. Other reviewed pages can be approved.')) + '</p>' +
104779
- sourceLocationsBlock;
104780
- document.querySelectorAll("[data-id]").forEach((button) => button.addEventListener("click", () => { selected = button.dataset.id; render(); }));
104781
- document.querySelectorAll("[data-action]").forEach((button) => button.addEventListener("click", () => setDecision(item.candidate_id, button.dataset.action)));
104782
- document.querySelectorAll("[data-group-toggle]").forEach((header) => header.addEventListener("click", () => toggleGroup(header.dataset.groupToggle)));
104783
- document.querySelectorAll("[data-group-status]").forEach((button) => button.addEventListener("click", (event) => {
104784
- event.stopPropagation();
104785
- setGroupDecision(button.dataset.group, button.dataset.groupStatus);
104786
- }));
104787
- }
104788
- function navigatePage(direction, pendingOnly = false) {
104789
- const items = visibleCandidates();
104790
- const current = items.findIndex((item) => item.candidate_id === selected);
104791
- for (let step = 1; step <= items.length; step++) {
104792
- const item = items[(current + direction * step + items.length * 2) % items.length];
104793
- if (!pendingOnly || decisions.get(item.candidate_id) === "pending") { selected = item.candidate_id; render(); detail.scrollTop = 0; return; }
104794
- }
104795
- }
104796
- search.addEventListener("input", render);
104797
- document.getElementById("previous-page").addEventListener("click", () => navigatePage(-1));
104798
- document.getElementById("next-page").addEventListener("click", () => navigatePage(1));
104799
- document.getElementById("next-pending").addEventListener("click", () => navigatePage(1, true));
104800
- document.getElementById("code-previous").addEventListener("click", () => { codePart = Math.max(0, codePart - 1); updatePayloadBox(); });
104801
- document.getElementById("code-next").addEventListener("click", () => { codePart++; updatePayloadBox(); });
104802
- function applyLanguage() {
104803
- document.documentElement.lang = language;
104804
- document.title = t("Context Review") + " - " + payloadScopeLabel;
104805
- const labels = {
104806
- "review-heading": "Context Review", "all-approved": "All approved", "all-rejected": "Omit all",
104807
- "payload-open": "Copy review results", "pages-label": "Pages to review", "label-approved": "approved",
104808
- "label-rejected": "omitted", "label-pending": "pending", "content-label": "Page content",
104809
- "previous-page": "Previous", "next-page": "Next", "next-pending": "Next pending",
104810
- "payload-title": "Review results", "code-previous": "Previous segment", "code-next": "Next segment",
104811
- "payload-close": "Close", "payload-copy": "Copy",
104812
- "code-help": "These choices take effect only after you send the review code back to the conversation. Each segment is at most 980 characters. Send every segment before applying.",
104813
- };
104814
- for (const [id, message] of Object.entries(labels)) document.getElementById(id).textContent = t(message);
104815
- search.placeholder = t("Search pages or modules");
104816
- search.setAttribute("aria-label", t("Search pages or modules"));
104817
- document.getElementById("filters").setAttribute("aria-label", t("candidate filters"));
104818
- payloadBox.setAttribute("aria-label", t("review code"));
104819
- theme.title = t("Toggle theme");
104820
- theme.setAttribute("aria-label", t("Toggle theme"));
104821
- const button = document.getElementById("language");
104822
- button.textContent = language === "zh-CN" ? "English" : "中文";
104823
- button.setAttribute("aria-label", language === "zh-CN" ? "Switch to English" : "切换为中文");
104824
- modalCopyState.textContent = "";
104825
- const scrollTop = detail.scrollTop;
104826
- render();
104827
- detail.scrollTop = scrollTop;
104828
- updatePayloadBox();
104829
- }
104830
- function toggleLanguage() {
104831
- language = language === "zh-CN" ? "en" : "zh-CN";
104832
- applyLanguage();
104833
- }
104834
- document.getElementById("language").addEventListener("click", toggleLanguage);
104835
- function effectiveTheme() {
104836
- return document.documentElement.dataset.theme || "light";
104837
- }
104838
- function updateThemeIcon() {
104839
- theme.textContent = effectiveTheme() === "dark" ? "☀️" : "\uD83C\uDF19";
104840
- }
104841
- theme.addEventListener("click", () => {
104842
- document.documentElement.dataset.theme = effectiveTheme() === "dark" ? "light" : "dark";
104843
- updateThemeIcon();
104844
- });
104845
- updateThemeIcon();
104846
- allApproved.addEventListener("click", () => setAllDecision("approved"));
104847
- allRejected.addEventListener("click", () => setAllDecision("rejected"));
104848
- filterApproved.addEventListener("change", render);
104849
- filterRejected.addEventListener("change", render);
104850
- filterPending.addEventListener("change", render);
104851
- payloadOpen.addEventListener("click", openPayloadModal);
104852
- payloadClose.addEventListener("click", closePayloadModal);
104853
- payloadCopy.addEventListener("click", copyPayload);
104854
- payloadModal.addEventListener("click", (event) => {
104855
- if (event.target === payloadModal) closePayloadModal();
104856
- });
104857
- document.addEventListener("keydown", (event) => {
104858
- if (event.key === "Escape" && !payloadModal.classList.contains("hidden")) closePayloadModal();
104859
- });
104860
- applyLanguage();
104861
- </script>
104862
- </body>
104863
- </html>
104864
- `;
104491
+ label: reviewScope,
104492
+ ids_sha256: candidateIdsHash(candidates.map((c) => c.record.candidate_id).sort()),
104493
+ candidates_sha256: candidateSetHash(candidates.map((c) => c.record))
104494
+ };
104495
+ return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${escapeReviewHtml(model.title)} · Review</title><style>${REVIEW_SITE_STYLES}</style></head><body>
104496
+ <header><button id="home">${escapeReviewHtml(model.title)}</button><nav id="top"></nav><div class="tools"><div class="counter" tabindex="0"><span id="counts"></span><div class="counter-pop" id="counter-pop"></div></div><button class="btn" id="all-approved"></button><button class="btn" id="all-rejected"></button><button class="btn primary" id="payload-open"></button><div class="guide" id="copy-guide"><span id="guide-countdown">10s</span><p id="guide-text"></p><button class="btn" id="guide-close"></button></div></div><button class="btn" id="theme" aria-label="Theme">◐</button><button class="btn" id="language"></button></header>
104497
+ <div class="layout"><aside id="tree"></aside><main><article id="article"></article></main></div>
104498
+ <footer id="footer" hidden><input id="revision-note" aria-label="Revision instructions"><button class="btn" id="revise-btn"></button><button class="btn" id="reject-btn"></button><button class="btn primary" id="approve-btn"></button></footer>
104499
+ <dialog id="bulk-dialog"><h2 id="bulk-title"></h2><p id="bulk-message"></p><div id="bulk-roots" hidden><strong id="bulk-roots-title"></strong><ul id="bulk-roots-list"></ul><label class="bulk-ack"><input type="checkbox" id="bulk-ack"><span id="bulk-ack-label"></span></label></div><div class="dialog-actions"><button class="btn" id="bulk-cancel"></button><button class="btn primary" id="bulk-confirm"></button></div></dialog>
104500
+ <dialog id="copy-dialog"><h2 id="copy-title"></h2><p id="copy-summary"></p><p id="copy-instructions"></p><textarea id="payload" readonly aria-label="Review code"></textarea><p id="copy-warning"></p><button class="btn" id="payload-close"></button></dialog>
104501
+ <script>const DATA=${reviewHtmlJson(model)};const SCOPE=${reviewHtmlJson(scope2)};const feedbackCodec=(${createReviewFeedbackCodec.toString()})();${REVIEW_SITE_CLIENT}</script></body></html>`;
104865
104502
  }
104866
104503
  function resolveOutputPath(projectRoot, outPath, reviewScope) {
104867
104504
  if (outPath === undefined)
104868
- return join85(projectRoot, REVIEW_HTML_ROOT, `${reviewScope}.html`);
104505
+ return join87(projectRoot, REVIEW_HTML_ROOT, `${reviewScope}.html`);
104869
104506
  return isAbsolute15(outPath) ? outPath : resolve28(projectRoot, outPath);
104870
104507
  }
104871
104508
  async function writeReviewHtml(input) {
@@ -104875,8 +104512,8 @@ async function writeReviewHtml(input) {
104875
104512
  }
104876
104513
  const candidates = reviewScope === "all" ? await collectAllReviewCandidates(input.projectRoot) : await collectReviewCandidates(input.projectRoot, reviewScope);
104877
104514
  const outPath = resolveOutputPath(input.projectRoot, input.out, reviewScope);
104878
- await mkdir30(dirname38(outPath), { recursive: true });
104879
- await writeFile24(outPath, renderReviewHtml(candidates, reviewScope), "utf8");
104515
+ await mkdir30(dirname37(outPath), { recursive: true });
104516
+ await writeFile24(outPath, renderReviewHtml(candidates, reviewScope, await collectReviewSiteModel(input.projectRoot, candidates)), "utf8");
104880
104517
  return {
104881
104518
  path: outPath,
104882
104519
  candidates: candidates.length,
@@ -104889,8 +104526,8 @@ async function writeReviewHtml(input) {
104889
104526
  init_productionRequirements();
104890
104527
  init_dist();
104891
104528
  init_atomicWrite();
104892
- import { mkdir as mkdir31, readFile as readFile67 } from "node:fs/promises";
104893
- import { join as join86 } from "node:path";
104529
+ import { mkdir as mkdir31, readFile as readFile69 } from "node:fs/promises";
104530
+ import { join as join88 } from "node:path";
104894
104531
  var REVIEW_BATCH_MAX_CANDIDATES = 6;
104895
104532
  var REVIEW_BATCH_MAX_BYTES = 512 * 1024;
104896
104533
  async function readerPurposes(projectRoot, sources) {
@@ -104966,15 +104603,16 @@ function buildCurrentReviewBatchDocuments(candidates) {
104966
104603
  });
104967
104604
  }
104968
104605
  async function materializeCurrentReviewBatchSet(input) {
104606
+ const feedback = await readPendingReviewFeedback(input.projectRoot, input.candidates);
104969
104607
  const batches = buildCurrentReviewBatchDocuments(input.candidates);
104970
104608
  const setDigest = digestText(batches.map((batch) => `${batch.task_key}:${batch.digest}`).join(`
104971
104609
  `));
104972
- const root2 = join86(input.projectRoot, ".tmp", "context-runtime", "review", `current-${setDigest.slice("sha256:".length)}`);
104610
+ const root2 = join88(input.projectRoot, ".tmp", "context-runtime", "review", `current-${setDigest.slice("sha256:".length)}`);
104973
104611
  await mkdir31(root2, { recursive: true });
104974
104612
  const entries2 = [];
104975
104613
  for (const batch of batches) {
104976
- const path4 = join86(input.projectRoot, ".tmp", "context-runtime", "review", `${batch.task_key}-${batch.digest.slice("sha256:".length)}.md`);
104977
- const existing = await readFile67(path4, "utf8").catch((error) => {
104614
+ const path4 = join88(input.projectRoot, ".tmp", "context-runtime", "review", `${batch.task_key}-${batch.digest.slice("sha256:".length)}.md`);
104615
+ const existing = await readFile69(path4, "utf8").catch((error) => {
104978
104616
  if (error.code === "ENOENT")
104979
104617
  return;
104980
104618
  throw error;
@@ -104986,6 +104624,7 @@ async function materializeCurrentReviewBatchSet(input) {
104986
104624
  }
104987
104625
  const content3 = [
104988
104626
  "# Current knowledge Review",
104627
+ ...feedback.length ? ["", "## Pending user revision instructions", ...feedback.map((item) => JSON.stringify(item)), "Apply these through their repair commands, then review the new candidates. Do not approve unchanged drafts to bypass feedback."] : [],
104989
104628
  "",
104990
104629
  "## Reader purposes",
104991
104630
  "",
@@ -105026,7 +104665,7 @@ async function materializeCurrentReviewBatchSet(input) {
105026
104665
  ].join(`
105027
104666
  `);
105028
104667
  const digest6 = digestText(content3);
105029
- const path3 = join86(root2, "index.md");
104668
+ const path3 = join88(root2, "index.md");
105030
104669
  await atomicWriteFile(path3, `${content3}
105031
104670
  `);
105032
104671
  return {
@@ -105059,11 +104698,11 @@ function shellQuote6(value) {
105059
104698
  }
105060
104699
  function receiptSetPath(receipts) {
105061
104700
  const token = digestText(JSON.stringify(receipts)).slice("sha256:".length);
105062
- return join87(".tmp", "context-runtime", "workflow", "read-receipts", `${token}.json`);
104701
+ return join89(".tmp", "context-runtime", "workflow", "read-receipts", `${token}.json`);
105063
104702
  }
105064
104703
  async function writeReceiptContinuation(input) {
105065
104704
  const path3 = receiptSetPath(input.receipts);
105066
- const absolutePath = join87(input.projectRoot, path3);
104705
+ const absolutePath = join89(input.projectRoot, path3);
105067
104706
  await writeJsonAtomic(absolutePath, input.receipts);
105068
104707
  const contextCommand = input.managed ? [
105069
104708
  "context",
@@ -105158,7 +104797,7 @@ async function materializeContextWorkflowResource(input) {
105158
104797
  const resourceId = workflowResourceId(input.resourceId);
105159
104798
  const content3 = renderContextWorkflowResource(resourceId, status);
105160
104799
  const location = await materializeResource(await loadContextWorkflowProvider(), resourceId, {
105161
- cache: join87(found.projectRoot, ".tmp", "context-runtime", "workflow", "resources"),
104800
+ cache: join89(found.projectRoot, ".tmp", "context-runtime", "workflow", "resources"),
105162
104801
  workspace: found.projectRoot,
105163
104802
  revision: input.revision,
105164
104803
  input: {
@@ -105190,7 +104829,7 @@ async function materializeContextWorkflowResource(input) {
105190
104829
  receipts: afterReadReceipts
105191
104830
  });
105192
104831
  const directResources = (status.workflow.current?.resources.required ?? []).filter((resource) => resource.read_state === "read-required" && resource.path !== undefined && resource.digest !== undefined);
105193
- const afterReadCommand = directResources.length === 0 ? continuation.command : `context resource acknowledge-current --revision ${shellQuote6(input.revision)}${authorityCommandOptions(authorities, "resource")} --resource-receipts ${shellQuote6(`@${join87(found.projectRoot, continuation.path)}`)} --format json`;
104832
+ const afterReadCommand = directResources.length === 0 ? continuation.command : `context resource acknowledge-current --revision ${shellQuote6(input.revision)}${authorityCommandOptions(authorities, "resource")} --resource-receipts ${shellQuote6(`@${join89(found.projectRoot, continuation.path)}`)} --format json`;
105194
104833
  return {
105195
104834
  protocol: "context.workflow.resource.v1",
105196
104835
  id: resourceId,
@@ -105251,14 +104890,14 @@ async function acknowledgeCurrentWorkflowResources(input) {
105251
104890
  const reevaluated = await reevaluateProjectStatusWorkflow({
105252
104891
  snapshot,
105253
104892
  resourceReceipts: normalizedReceipts,
105254
- resourceReceiptsReference: `@${join87(found.projectRoot, continuation.path)}`
104893
+ resourceReceiptsReference: `@${join89(found.projectRoot, continuation.path)}`
105255
104894
  });
105256
104895
  return {
105257
104896
  ...reevaluated,
105258
104897
  resourceAcknowledgement: {
105259
104898
  protocol: "context.workflow.resource-receipts.v1",
105260
104899
  acknowledged: directResources.length,
105261
- receiptReference: `@${join87(found.projectRoot, continuation.path)}`
104900
+ receiptReference: `@${join89(found.projectRoot, continuation.path)}`
105262
104901
  }
105263
104902
  };
105264
104903
  }
@@ -105304,9 +104943,9 @@ init_cliFeedback();
105304
104943
  init_errors3();
105305
104944
  init_exitCode();
105306
104945
  init_workspace();
105307
- import { readFile as readFile68 } from "node:fs/promises";
105308
- import { isAbsolute as isAbsolute16, join as join88, sep as sep6, resolve as resolve29 } from "node:path";
105309
- var RECEIPT_DIRECTORY = join88(".tmp", "context-runtime", "workflow", "read-receipts");
104946
+ import { readFile as readFile70 } from "node:fs/promises";
104947
+ import { isAbsolute as isAbsolute16, join as join90, sep as sep6, resolve as resolve29 } from "node:path";
104948
+ var RECEIPT_DIRECTORY = join90(".tmp", "context-runtime", "workflow", "read-receipts");
105310
104949
  function workflowResourceReceiptCwd(value, cwd) {
105311
104950
  if (value === undefined || !value.startsWith("@"))
105312
104951
  return cwd;
@@ -105324,7 +104963,7 @@ async function receiptDocument(value, cwd) {
105324
104963
  let source2 = value;
105325
104964
  if (value.startsWith("@")) {
105326
104965
  try {
105327
- source2 = await readFile68(resolve29(cwd, value.slice(1)), "utf8");
104966
+ source2 = await readFile70(resolve29(cwd, value.slice(1)), "utf8");
105328
104967
  } catch (error) {
105329
104968
  const ioCode = error !== null && typeof error === "object" && "code" in error && typeof error.code === "string" ? error.code : undefined;
105330
104969
  throw new ContextError(ExitCode.UserError, "resource read receipt file is unavailable", {
@@ -105485,33 +105124,33 @@ function runSuccessBaseBody(input) {
105485
105124
  `log: ${input.logPath}`
105486
105125
  ];
105487
105126
  }
105488
- function appendCaptureFileRunBody(body, result) {
105127
+ function appendCaptureFileRunBody(body2, result) {
105489
105128
  const nextAction = nextActionCommand(result.next_action);
105490
- body.push(`source: file:${result.source.name}`, `include: ${result.source.include.join(", ")}`, `documents: ${result.documents.length}`, `snapshot: ${result.snapshot.manifest}`, `snapshot hash: ${result.snapshot.snapshot_hash}`, `changed: ${result.snapshot.changed ? "yes" : "no"}`);
105129
+ body2.push(`source: file:${result.source.name}`, `include: ${result.source.include.join(", ")}`, `documents: ${result.documents.length}`, `snapshot: ${result.snapshot.manifest}`, `snapshot hash: ${result.snapshot.snapshot_hash}`, `changed: ${result.snapshot.changed ? "yes" : "no"}`);
105491
105130
  if (nextAction !== undefined)
105492
- body.push(`next action: ${nextAction}`);
105131
+ body2.push(`next action: ${nextAction}`);
105493
105132
  for (const document4 of result.documents.slice(0, 8)) {
105494
- body.push(`document ${document4.path}: ${document4.title} (${document4.line_count} line(s))`);
105133
+ body2.push(`document ${document4.path}: ${document4.title} (${document4.line_count} line(s))`);
105495
105134
  }
105496
105135
  }
105497
- function appendCaptureLarkRunBody(body, result) {
105136
+ function appendCaptureLarkRunBody(body2, result) {
105498
105137
  const nextAction = nextActionCommand(result.next_action);
105499
- body.push(`source: lark:${result.source.name}`, `identity: ${result.source.identity}`, `documents: ${result.documents.length}`, `assets: ${result.assets.length}`, `snapshot: ${result.snapshot.manifest}`, `snapshot hash: ${result.snapshot.snapshot_hash}`, `changed: ${result.snapshot.changed ? "yes" : "no"}`);
105138
+ body2.push(`source: lark:${result.source.name}`, `identity: ${result.source.identity}`, `documents: ${result.documents.length}`, `assets: ${result.assets.length}`, `snapshot: ${result.snapshot.manifest}`, `snapshot hash: ${result.snapshot.snapshot_hash}`, `changed: ${result.snapshot.changed ? "yes" : "no"}`);
105500
105139
  if (nextAction !== undefined)
105501
- body.push(`next action: ${nextAction}`);
105140
+ body2.push(`next action: ${nextAction}`);
105502
105141
  for (const document4 of result.documents.slice(0, 8)) {
105503
- body.push(`document ${document4.path}: ${document4.title} (${document4.line_count} line(s))`);
105142
+ body2.push(`document ${document4.path}: ${document4.title} (${document4.line_count} line(s))`);
105504
105143
  }
105505
105144
  }
105506
- function appendRunResultBody(body, result) {
105145
+ function appendRunResultBody(body2, result) {
105507
105146
  if (isCaptureFileRunResult(result))
105508
- appendCaptureFileRunBody(body, result);
105147
+ appendCaptureFileRunBody(body2, result);
105509
105148
  if (isCaptureLarkRunResult(result))
105510
- appendCaptureLarkRunBody(body, result);
105149
+ appendCaptureLarkRunBody(body2, result);
105511
105150
  if (result !== null && typeof result === "object" && !Array.isArray(result) && "kind" in result && (result.kind === "semantic.rules.view.result" || result.kind === "diagnostics.view.result") && "next_action" in result && result.next_action !== null && typeof result.next_action === "object" && !Array.isArray(result.next_action)) {
105512
105151
  const nextCommand = nextActionCommand(result.next_action);
105513
105152
  if (nextCommand !== undefined)
105514
- body.push(`next action: ${nextCommand}`);
105153
+ body2.push(`next action: ${nextCommand}`);
105515
105154
  }
105516
105155
  }
105517
105156
  function writeRunSuccess(input) {
@@ -105532,14 +105171,14 @@ function writeRunSuccess(input) {
105532
105171
  `);
105533
105172
  return;
105534
105173
  }
105535
- const body = runSuccessBaseBody(input);
105536
- appendRunResultBody(body, input.result);
105174
+ const body2 = runSuccessBaseBody(input);
105175
+ appendRunResultBody(body2, input.result);
105537
105176
  process.stdout.write(formatFeedback({
105538
105177
  symbol: "✓",
105539
105178
  action: "ran",
105540
105179
  subject: input.plan.phase.id,
105541
105180
  headline: input.plan.phase.kind,
105542
- body
105181
+ body: body2
105543
105182
  }));
105544
105183
  }
105545
105184
  function compactDiagnostics(record4) {
@@ -105571,15 +105210,15 @@ function compactJsonResult(result, verbose) {
105571
105210
  // src/project/runLog.ts
105572
105211
  import { randomUUID as randomUUID5 } from "node:crypto";
105573
105212
  import { mkdir as mkdir32, writeFile as writeFile25 } from "node:fs/promises";
105574
- import { dirname as dirname39, join as join91 } from "node:path";
105213
+ import { dirname as dirname38, join as join93 } from "node:path";
105575
105214
  var createPhaseRunId = () => {
105576
105215
  const timestamp = new Date().toISOString().replace(/[-:.TZ]/gu, "");
105577
105216
  return `run_${timestamp}_${randomUUID5().slice(0, 8)}`;
105578
105217
  };
105579
105218
  async function writePhaseRunLog(input) {
105580
- const relPath = join91(".tmp", "context-runtime", "runs", `${input.runId}.json`);
105581
- const absPath = join91(input.projectRoot, relPath);
105582
- await mkdir32(dirname39(absPath), { recursive: true });
105219
+ const relPath = join93(".tmp", "context-runtime", "runs", `${input.runId}.json`);
105220
+ const absPath = join93(input.projectRoot, relPath);
105221
+ await mkdir32(dirname38(absPath), { recursive: true });
105583
105222
  await writeFile25(absPath, `${JSON.stringify({
105584
105223
  run_id: input.runId,
105585
105224
  phase_id: input.phase.id,
@@ -105784,7 +105423,7 @@ function writeRunPlan(plan, format2, preview, previewError) {
105784
105423
  `);
105785
105424
  return;
105786
105425
  }
105787
- const body = [
105426
+ const body2 = [
105788
105427
  `dry-run: ${plan.dryRun ? "yes" : "no"}`,
105789
105428
  `reads: ${plan.phase.reads.length > 0 ? plan.phase.reads.join(", ") : "none"}`,
105790
105429
  `writes: ${plan.phase.writes.length > 0 ? plan.phase.writes.join(", ") : "none"}`,
@@ -105796,7 +105435,7 @@ function writeRunPlan(plan, format2, preview, previewError) {
105796
105435
  action: "planned",
105797
105436
  subject: plan.phase.id,
105798
105437
  headline: plan.phase.kind,
105799
- body
105438
+ body: body2
105800
105439
  }));
105801
105440
  }
105802
105441
  function customPhaseContext(input) {
@@ -106318,7 +105957,7 @@ init_debugTrace();
106318
105957
  // src/project/workflow/workflowExecutionRuntime.ts
106319
105958
  init_src();
106320
105959
  init_debugTrace();
106321
- import { createHash as createHash27 } from "node:crypto";
105960
+ import { createHash as createHash28 } from "node:crypto";
106322
105961
  import { spawn as spawn5 } from "node:child_process";
106323
105962
  function digestText2(value, includeTail) {
106324
105963
  const filtered = redactIndexerOutputText({
@@ -106328,7 +105967,7 @@ function digestText2(value, includeTail) {
106328
105967
  const bytes = Buffer.byteLength(filtered);
106329
105968
  return {
106330
105969
  bytes,
106331
- sha256: createHash27("sha256").update(filtered).digest("hex"),
105970
+ sha256: createHash28("sha256").update(filtered).digest("hex"),
106332
105971
  ...includeTail && filtered.length > 0 ? { tail: filtered.slice(-8192) } : {}
106333
105972
  };
106334
105973
  }
@@ -106521,11 +106160,150 @@ execution scope cleanup failed`, true)
106521
106160
  // src/project/workflow/workflowInProcessActions.ts
106522
106161
  init_workflowFacts();
106523
106162
 
106163
+ // src/project/reviewCode.ts
106164
+ function createReviewCodeCodec() {
106165
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
106166
+ function checksum(text9) {
106167
+ let crc = 4294967295;
106168
+ for (let i2 = 0;i2 < text9.length; i2++) {
106169
+ crc ^= text9.charCodeAt(i2);
106170
+ for (let bit = 0;bit < 8; bit++)
106171
+ crc = crc >>> 1 ^ (crc & 1 ? 3988292384 : 0);
106172
+ }
106173
+ return ((crc ^ 4294967295) >>> 0).toString(16).padStart(8, "0");
106174
+ }
106175
+ function pack(bytes) {
106176
+ let value = 0, bits = 0, result = "";
106177
+ for (const byte of bytes) {
106178
+ value = value << 8 | byte;
106179
+ bits += 8;
106180
+ while (bits >= 6) {
106181
+ bits -= 6;
106182
+ result += alphabet[value >>> bits & 63];
106183
+ }
106184
+ }
106185
+ if (bits)
106186
+ result += alphabet[value << 6 - bits & 63];
106187
+ return result;
106188
+ }
106189
+ function unpack(text9) {
106190
+ if (!/^[A-Za-z0-9_-]*$/.test(text9))
106191
+ throw new Error("Invalid review code encoding");
106192
+ let value = 0, bits = 0;
106193
+ const bytes = [];
106194
+ for (const char of text9) {
106195
+ value = value << 6 | alphabet.indexOf(char);
106196
+ bits += 6;
106197
+ if (bits >= 8) {
106198
+ bits -= 8;
106199
+ bytes.push(value >>> bits & 255);
106200
+ }
106201
+ }
106202
+ if (pack(bytes) !== text9)
106203
+ throw new Error("Noncanonical review code encoding");
106204
+ return bytes;
106205
+ }
106206
+ function hash4(text9) {
106207
+ if (!/^[a-f0-9]{64}$/.test(text9))
106208
+ throw new Error("Review requires a complete candidate digest");
106209
+ return pack(Array.from({ length: 32 }, (_, i2) => Number.parseInt(text9.slice(i2 * 2, i2 * 2 + 2), 16)));
106210
+ }
106211
+ function unhash(text9) {
106212
+ const bytes = unpack(text9);
106213
+ if (bytes.length !== 32)
106214
+ throw new Error("Invalid candidate digest");
106215
+ return bytes.map((byte) => byte.toString(16).padStart(2, "0")).join("");
106216
+ }
106217
+ function encode(scope2, idsHash, contentHash2, statuses) {
106218
+ if (!/^[a-z][a-z0-9-]*$/.test(scope2) || !statuses.length || statuses.length > 1e6 || statuses.some((status) => status !== "approved" && status !== "rejected" && status !== "pending")) {
106219
+ throw new Error("Select at least one review decision and keep undecided pages pending");
106220
+ }
106221
+ if (statuses.every((s) => s === "pending"))
106222
+ throw new Error("Select at least one review decision");
106223
+ const mode = statuses.every((s) => s === "approved") ? "a" : statuses.every((s) => s === "rejected") ? "r" : statuses.includes("pending") ? "p" : "b";
106224
+ const bytes = Array(Math.ceil(statuses.length / (mode === "p" ? 4 : 8))).fill(0);
106225
+ if (mode === "p")
106226
+ statuses.forEach((s, i2) => {
106227
+ bytes[i2 >> 2] |= (s === "approved" ? 1 : s === "rejected" ? 2 : 0) << i2 % 4 * 2;
106228
+ });
106229
+ if (mode === "b")
106230
+ statuses.forEach((s, i2) => {
106231
+ if (s === "rejected")
106232
+ bytes[i2 >> 3] |= 1 << i2 % 8;
106233
+ });
106234
+ const body2 = ["CR1", scope2, statuses.length, hash4(idsHash), hash4(contentHash2), mode, mode === "b" || mode === "p" ? pack(bytes) : ""].join(".");
106235
+ const code3 = `${body2}.${checksum(body2)}`;
106236
+ if (code3.length <= 980)
106237
+ return [code3];
106238
+ const total = Math.ceil(code3.length / 900);
106239
+ if (total > 200)
106240
+ throw new Error("Review decisions exceed 200 segments; use a smaller collection scope");
106241
+ const identity = checksum(code3);
106242
+ return Array.from({ length: total }, (_, i2) => `CRP1.${identity}.${i2 + 1}.${total}.${code3.slice(i2 * 900, (i2 + 1) * 900)}`);
106243
+ }
106244
+ function decode2(input) {
106245
+ if (input.length > 250000)
106246
+ throw new Error("Review code exceeds the supported size");
106247
+ const lines = input.trim().split(/\s+/);
106248
+ let code3 = lines[0];
106249
+ if (code3.startsWith("CRP1.")) {
106250
+ const parts = new Map;
106251
+ let identity = "", total = 0;
106252
+ for (const line of lines) {
106253
+ const match = /^CRP1\.([a-f0-9]{8})\.([1-9][0-9]*)\.([1-9][0-9]*)\.(.+)$/.exec(line);
106254
+ if (!match || line.length > 980)
106255
+ throw new Error("Invalid review code segment");
106256
+ const index2 = Number(match[2]), count2 = Number(match[3]);
106257
+ if (count2 > 200 || index2 > count2 || parts.has(index2) || total && (total !== count2 || identity !== match[1])) {
106258
+ throw new Error("Duplicate or mixed review code segments");
106259
+ }
106260
+ identity = match[1];
106261
+ total = count2;
106262
+ parts.set(index2, match[4]);
106263
+ }
106264
+ if (parts.size !== total)
106265
+ throw new Error(`Missing review code segments: received ${parts.size} of ${total}; collect all segments before applying`);
106266
+ code3 = Array.from({ length: total }, (_, i2) => parts.get(i2 + 1)).join("");
106267
+ if (checksum(code3) !== identity)
106268
+ throw new Error("Review code segment checksum mismatch");
106269
+ } else if (lines.length !== 1 || code3.length > 980)
106270
+ throw new Error("Copy each complete review code segment unchanged");
106271
+ const fields = code3.split(".");
106272
+ if (fields.length !== 8 || fields[0] !== "CR1" || checksum(fields.slice(0, 7).join(".")) !== fields[7]) {
106273
+ throw new Error("Review code is damaged or unsupported; copy it again from the report");
106274
+ }
106275
+ const [, scope2, countText, ids, content3, mode, data2] = fields;
106276
+ if (!/^[a-z][a-z0-9-]*$/.test(scope2) || !/^[1-9][0-9]*$/.test(countText))
106277
+ throw new Error("Invalid review scope");
106278
+ const count = Number(countText);
106279
+ if (count > 1e6 || !["a", "r", "b", "p"].includes(mode))
106280
+ throw new Error("Invalid review decisions");
106281
+ const bytes = unpack(data2);
106282
+ const perByte = mode === "p" ? 4 : 8;
106283
+ if (mode === "b" || mode === "p" ? bytes.length !== Math.ceil(count / perByte) || count % perByte !== 0 && bytes.at(-1) >>> count % perByte * (mode === "p" ? 2 : 1) !== 0 : data2 !== "") {
106284
+ throw new Error("Invalid review decision bitmap");
106285
+ }
106286
+ const statuses = Array.from({ length: count }, (_, i2) => {
106287
+ if (mode === "p") {
106288
+ const value = bytes[i2 >> 2] >>> i2 % 4 * 2 & 3;
106289
+ if (value === 3)
106290
+ throw new Error("Invalid pending review bitmap");
106291
+ return value === 1 ? "approved" : value === 2 ? "rejected" : "pending";
106292
+ }
106293
+ return mode === "r" || mode === "b" && bytes[i2 >> 3] & 1 << i2 % 8 ? "rejected" : "approved";
106294
+ });
106295
+ if (statuses.every((s) => s === "pending"))
106296
+ throw new Error("Review contains no decisions");
106297
+ return { scope: scope2, count, idsHash: unhash(ids), contentHash: unhash(content3), statuses };
106298
+ }
106299
+ return { encode, decode: decode2 };
106300
+ }
106301
+
106524
106302
  // src/project/review.ts
106525
106303
  init_cliFeedback();
106526
106304
  init_errors3();
106527
106305
  init_exitCode();
106528
- import { readFile as readFile73 } from "node:fs/promises";
106306
+ import { readFile as readFile75 } from "node:fs/promises";
106529
106307
  import { isAbsolute as isAbsolute18, resolve as resolve30 } from "node:path";
106530
106308
 
106531
106309
  // src/project/reviewApply.ts
@@ -106538,8 +106316,8 @@ init_writeLock();
106538
106316
  init_reviewApplyIndexer();
106539
106317
  init_approvedKnowledgeSnapshots();
106540
106318
  import { existsSync as existsSync25 } from "node:fs";
106541
- import { readFile as readFile71 } from "node:fs/promises";
106542
- import { join as join92 } from "node:path";
106319
+ import { readFile as readFile73 } from "node:fs/promises";
106320
+ import { join as join94 } from "node:path";
106543
106321
 
106544
106322
  // src/project/reviewCandidateAuthority.ts
106545
106323
  init_src2();
@@ -106636,7 +106414,7 @@ async function prepareApprovedPage(input) {
106636
106414
  next: "Refresh the current production or article revision, then reopen Review before approval."
106637
106415
  });
106638
106416
  }
106639
- const relPath = join92("knowledge", input.record.path);
106417
+ const relPath = join94("knowledge", input.record.path);
106640
106418
  const existingView = findApprovedPageForArticleId(input.record.indexer_candidate.artifact_ref, input.approvedPageIndex);
106641
106419
  const previousPath = input.record.approved_revision?.previous_path;
106642
106420
  if (previousPath !== undefined && (!isSafeKnowledgeTargetPath(previousPath.split("/")[0], previousPath) || previousPath.includes("\\")))
@@ -106651,13 +106429,13 @@ async function prepareApprovedPage(input) {
106651
106429
  next: "Resolve the approved page path migration explicitly before approving this candidate."
106652
106430
  });
106653
106431
  }
106654
- const absPath = join92(input.projectRoot, relPath);
106655
- const existing = existsSync25(absPath) ? await readFile71(absPath, "utf8") : undefined;
106432
+ const absPath = join94(input.projectRoot, relPath);
106433
+ const existing = existsSync25(absPath) ? await readFile73(absPath, "utf8") : undefined;
106656
106434
  let previous3;
106657
106435
  if (previousPath !== undefined) {
106658
106436
  if (existing !== undefined || existingView?.relPath !== `knowledge/${previousPath}`)
106659
106437
  throw new TypeError("Page move destination or original identity changed; refresh its revision.");
106660
- previous3 = { path: `knowledge/${previousPath}`, content: await readFile71(join92(input.projectRoot, "knowledge", previousPath), "utf8") };
106438
+ previous3 = { path: `knowledge/${previousPath}`, content: await readFile73(join94(input.projectRoot, "knowledge", previousPath), "utf8") };
106661
106439
  }
106662
106440
  if (input.record.approved_revision !== undefined) {
106663
106441
  const base = previous3?.content ?? existing;
@@ -106704,7 +106482,7 @@ async function prepareApprovedPage(input) {
106704
106482
  }
106705
106483
  async function readProjectFileMaybe(projectRoot, relPath) {
106706
106484
  try {
106707
- return await readFile71(join92(projectRoot, relPath), "utf8");
106485
+ return await readFile73(join94(projectRoot, relPath), "utf8");
106708
106486
  } catch (error) {
106709
106487
  if (error !== null && typeof error === "object" && "code" in error && error.code === "ENOENT") {
106710
106488
  return;
@@ -106834,6 +106612,36 @@ async function applyReviewDecisions(input) {
106834
106612
  const rows = await readCandidateRecords(input.projectRoot);
106835
106613
  const nextRows = [...rows];
106836
106614
  const decisions = expandReviewPayload(input.payload, rows);
106615
+ const scoped = rows.filter((r) => r.status === "draft" && (input.payload.scope?.kind === "all" || r.collection === input.payload.collection)).sort((a, b) => a.candidate_id < b.candidate_id ? -1 : 1);
106616
+ if (input.payload.baseline_hash && input.payload.baseline_hash !== await reviewSiteBaselineHash(input.projectRoot, scoped.map((r) => r.approved_revision?.previous_path ?? r.path))) {
106617
+ throw new ContextError(ExitCode.WorkspaceStateError, "Review navigation or approved baseline changed; generate a fresh report", {
106618
+ category: ErrorCategory.WorkspaceStateInvalid,
106619
+ code: "review-baseline-stale",
106620
+ next: "context review html --all --format json"
106621
+ });
106622
+ }
106623
+ const repairIds = new Set;
106624
+ const repairs = (input.payload.feedback_repairs ?? []).map((repair) => {
106625
+ const row = scoped[repair.index];
106626
+ if (!Number.isInteger(repair.index) || !row || repairIds.has(repair.index) || !repair.instruction.trim() || input.payload.encoded_statuses?.[repair.index] !== "pending")
106627
+ throw new Error("Invalid review revision request");
106628
+ repairIds.add(repair.index);
106629
+ const quote = (value) => "'" + value.replace(/'/gu, "'\\''") + "'";
106630
+ return {
106631
+ candidate_id: row.candidate_id,
106632
+ path: row.path,
106633
+ fingerprint: row.fingerprint,
106634
+ instruction: repair.instruction,
106635
+ command: `context revise ${quote(row.candidate_id)} --instruction ${quote(repair.instruction)} --format json`
106636
+ };
106637
+ });
106638
+ const feedbackPath = repairs.length ? `.tmp/context-runtime/review-feedback/${indexerProtocolDigest(input.payload).replace("sha256:", "")}.json` : undefined;
106639
+ const feedbackTarget = feedbackPath === undefined ? undefined : reviewFileTarget({
106640
+ path: feedbackPath,
106641
+ baseContent: await readProjectFileMaybe(input.projectRoot, feedbackPath),
106642
+ targetContent: JSON.stringify({ created_at: now, repairs }, null, 2) + `
106643
+ `
106644
+ });
106837
106645
  const approvesAnyCandidate = decisions.some((decision) => decision.status === "approved");
106838
106646
  const candidateAuthority = approvesAnyCandidate ? await loadReviewCandidateAuthority(input.projectRoot) : undefined;
106839
106647
  const approvedPageIndex = approvesAnyCandidate ? await buildApprovedArticleIndex(input.projectRoot) : {
@@ -106897,7 +106705,7 @@ async function applyReviewDecisions(input) {
106897
106705
  });
106898
106706
  }
106899
106707
  seenApprovedIds.set(approvedRef, row.candidate_id);
106900
- const approvedPath = join92("knowledge", row.path);
106708
+ const approvedPath = join94("knowledge", row.path);
106901
106709
  const previousPathCandidate = seenApprovedPaths.get(knowledgeTargetPathKey(approvedPath));
106902
106710
  if (previousPathCandidate !== undefined) {
106903
106711
  throw new ContextError(ExitCode.UserError, `multiple approved review decisions target the same knowledge path: ${approvedPath}`, {
@@ -106947,13 +106755,14 @@ async function applyReviewDecisions(input) {
106947
106755
  for (const path3 of approvedPageIndex.byRelPath.keys()) {
106948
106756
  if (pagesToWrite.some((page) => page.relPath === path3 || page.previous?.path === path3))
106949
106757
  continue;
106950
- const before = await readFile71(join92(input.projectRoot, path3), "utf8");
106758
+ const before = await readFile73(join94(input.projectRoot, path3), "utf8");
106951
106759
  const local = path3.replace(/^knowledge\//u, "");
106952
106760
  const after = moveKnowledgeLinkTargets2(before, local, local, moved);
106953
106761
  navigationTargets.push(reviewFileTarget({ path: path3, baseContent: before, targetContent: after }));
106954
106762
  }
106955
106763
  }
106956
106764
  const targets = [
106765
+ feedbackTarget,
106957
106766
  await prepareApprovedKnowledgeSnapshotTarget({ projectRoot: input.projectRoot, pages: pagesToWrite, candidates: rows }),
106958
106767
  ...navigationTargets,
106959
106768
  ...pagesToWrite.flatMap((page) => page.previous === undefined ? [] : [reviewFileTarget({
@@ -106989,6 +106798,7 @@ async function applyReviewDecisions(input) {
106989
106798
  }
106990
106799
  return {
106991
106800
  applied: decisions.length,
106801
+ ...feedbackPath ? { repairs, feedback_path: feedbackPath } : {},
106992
106802
  approved,
106993
106803
  rejected,
106994
106804
  unchanged,
@@ -107001,13 +106811,13 @@ async function applyReviewDecisions(input) {
107001
106811
  }
107002
106812
 
107003
106813
  // src/project/reviewMaintenance.ts
107004
- import { readFile as readFile72, writeFile as writeFile26 } from "node:fs/promises";
106814
+ import { readFile as readFile74, writeFile as writeFile26 } from "node:fs/promises";
107005
106815
  init_writeLock();
107006
106816
  init_verifyFrontmatter();
107007
106817
  function deprecateApprovedPage(input) {
107008
106818
  return withProjectWriteLock(input.projectRoot, "deprecate-article", async () => {
107009
106819
  const page = await approvedPageForArticleId(input.projectRoot, input.viewRef);
107010
- const original = await readFile72(page.path, "utf8");
106820
+ const original = await readFile74(page.path, "utf8");
107011
106821
  const content3 = parseFrontmatterLoose(original).deprecated === true ? original : updateFrontmatter(original, (metadata) => ({ ...metadata, deprecated: true, timestamp: new Date().toISOString() }));
107012
106822
  const changed = content3 !== original;
107013
106823
  if (changed)
@@ -107026,13 +106836,13 @@ function deprecateApprovedPage(input) {
107026
106836
  init_candidateLedger();
107027
106837
 
107028
106838
  // src/project/localHtmlReport.ts
107029
- import { execFile as execFile10 } from "node:child_process";
107030
- import { isAbsolute as isAbsolute17, join as join93 } from "node:path";
106839
+ import { execFile as execFile11 } from "node:child_process";
106840
+ import { isAbsolute as isAbsolute17, join as join95 } from "node:path";
107031
106841
  import { pathToFileURL as pathToFileURL2 } from "node:url";
107032
- import { promisify as promisify10 } from "node:util";
107033
- var execFileAsync5 = promisify10(execFile10);
106842
+ import { promisify as promisify11 } from "node:util";
106843
+ var execFileAsync5 = promisify11(execFile11);
107034
106844
  function htmlReportReference(input) {
107035
- const absolutePath = isAbsolute17(input.path) ? input.path : join93(input.projectRoot, input.path);
106845
+ const absolutePath = isAbsolute17(input.path) ? input.path : join95(input.projectRoot, input.path);
107036
106846
  return {
107037
106847
  format: "html",
107038
106848
  path: input.path,
@@ -107204,7 +107014,7 @@ function parseReviewPayloadText(raw) {
107204
107014
  async function readReviewPayloadFile(filePath2) {
107205
107015
  let raw;
107206
107016
  try {
107207
- raw = await readFile73(filePath2, "utf8");
107017
+ raw = await readFile75(filePath2, "utf8");
107208
107018
  } catch (error) {
107209
107019
  const message = error instanceof Error ? error.message : String(error);
107210
107020
  throw new ContextError(ExitCode.UserError, `review payload file cannot be read: ${filePath2}`, {
@@ -107216,6 +107026,24 @@ async function readReviewPayloadFile(filePath2) {
107216
107026
  }
107217
107027
  if (raw.trim().startsWith("CR")) {
107218
107028
  try {
107029
+ if (raw.trim().startsWith("CR2.")) {
107030
+ const feedback = createReviewFeedbackCodec().decode(raw);
107031
+ const collection2 = feedback.scope === "all" ? undefined : assertCollection(feedback.scope);
107032
+ return {
107033
+ decisions: [],
107034
+ encoded_statuses: feedback.statuses.map((s) => s === "revised" ? "pending" : s),
107035
+ feedback_repairs: feedback.repairs,
107036
+ baseline_hash: feedback.baselineHash,
107037
+ ...collection2 === undefined ? {} : { collection: collection2 },
107038
+ scope: {
107039
+ kind: collection2 === undefined ? "all" : "collection",
107040
+ ...collection2 === undefined ? {} : { collection: collection2 },
107041
+ count: feedback.statuses.length,
107042
+ ids_sha256: feedback.idsHash,
107043
+ candidates_sha256: feedback.contentHash
107044
+ }
107045
+ };
107046
+ }
107219
107047
  const decoded = createReviewCodeCodec().decode(raw);
107220
107048
  const collection = decoded.scope === "all" ? undefined : assertCollection(decoded.scope);
107221
107049
  return {
@@ -107233,7 +107061,7 @@ async function readReviewPayloadFile(filePath2) {
107233
107061
  } catch (error) {
107234
107062
  throw new ContextError(ExitCode.UserError, error instanceof Error ? error.message : String(error), {
107235
107063
  category: ErrorCategory.UserInputInvalid,
107236
- next: "Copy all review code segments unchanged from the current HTML report into one input file, one per line; apply only after all segments are present."
107064
+ next: "Copy the complete review code and all following revision instruction lines unchanged from the current report into one input file. Older segmented codes require every segment."
107237
107065
  });
107238
107066
  }
107239
107067
  }
@@ -107253,6 +107081,7 @@ function formatApplyResult(result, format2) {
107253
107081
  `rejected: ${result.rejected}`,
107254
107082
  `materialized: ${result.materialized}`,
107255
107083
  `removed: ${result.removed}`,
107084
+ ...(result.repairs ?? []).map((r) => `repair ${r.path}: ${r.command}`),
107256
107085
  `unchanged: ${result.unchanged}`,
107257
107086
  `candidate file: ${result.candidateFileUpdated ? "updated" : "unchanged"}`,
107258
107087
  ...result.pages.map((page) => `page: ${page}`),
@@ -107499,6 +107328,7 @@ async function runReviewApproveAllCommand(input) {
107499
107328
  `materialized: ${result.materialized}`,
107500
107329
  `unchanged: ${result.unchanged}`,
107501
107330
  `removed: ${result.removed}`,
107331
+ ...(result.repairs ?? []).map((r) => `repair ${r.path}: ${r.command}`),
107502
107332
  ...continuation === undefined ? [] : [`workflow: ${continuation.state}`, continuation.stop.message]
107503
107333
  ]
107504
107334
  }));
@@ -108035,8 +107865,8 @@ init_exitCode();
108035
107865
  init_maintenanceStorage();
108036
107866
  init_productionFeedback();
108037
107867
  import { randomUUID as randomUUID6 } from "node:crypto";
108038
- import { readFile as readFile74 } from "node:fs/promises";
108039
- import { join as join94 } from "node:path";
107868
+ import { readFile as readFile76 } from "node:fs/promises";
107869
+ import { join as join96 } from "node:path";
108040
107870
  async function beginProductionRevision(input) {
108041
107871
  return withProductionFeedback({ operation: "revision" }, () => withProjectWriteLock(input.projectRoot, "production-revision", async () => {
108042
107872
  await recoverDurableMultiFileTransactions(input.projectRoot);
@@ -108075,7 +107905,7 @@ async function beginProductionRevision(input) {
108075
107905
  const formal = approved.byPath.get(path3);
108076
107906
  if (!prior && !formal)
108077
107907
  throw invalid2("Write the current task first; there is no article draft to revise yet.");
108078
- const markdown = prior?.body ?? await readFile74(await safeProjectTarget(input.projectRoot, join94("knowledge", path3)), "utf8");
107908
+ const markdown = prior?.body ?? await readFile76(await safeProjectTarget(input.projectRoot, join96("knowledge", path3)), "utf8");
108079
107909
  const sections = prior?.indexer_candidate.sections.map((section) => ({ id: section.section_key, references: section.references })) ?? formal?.sections;
108080
107910
  const sources = [];
108081
107911
  for (const source2 of owner.sources) {
@@ -108251,8 +108081,8 @@ init_actionInputWorkspace();
108251
108081
  init_cliFeedback();
108252
108082
  init_errors3();
108253
108083
  init_exitCode();
108254
- var import_yaml40 = __toESM(require_dist(), 1);
108255
- import { readFile as readFile75 } from "node:fs/promises";
108084
+ var import_yaml41 = __toESM(require_dist(), 1);
108085
+ import { readFile as readFile77 } from "node:fs/promises";
108256
108086
  function userInputError2(message, detail = {}) {
108257
108087
  return new ContextError(ExitCode.UserError, message, {
108258
108088
  category: ErrorCategory.UserInputInvalid,
@@ -108263,7 +108093,7 @@ function parsePayloadText(raw) {
108263
108093
  const trimmed = raw.trimStart();
108264
108094
  if (trimmed.startsWith("{") || trimmed.startsWith("["))
108265
108095
  return JSON.parse(raw);
108266
- return import_yaml40.default.parse(raw);
108096
+ return import_yaml41.default.parse(raw);
108267
108097
  }
108268
108098
  function isCompleteJsonLine(raw) {
108269
108099
  if (!raw.endsWith(`
@@ -108321,7 +108151,7 @@ async function readPayloadTextFromStdin(stdin) {
108321
108151
  }
108322
108152
  async function readPayloadText(path3) {
108323
108153
  if (path3 !== "-")
108324
- return readFile75(path3, "utf8");
108154
+ return readFile77(path3, "utf8");
108325
108155
  return readPayloadTextFromStdin(process.stdin);
108326
108156
  }
108327
108157
  async function readYamlOrJsonInput(input) {
@@ -108683,7 +108513,7 @@ function registerRuntimeEventLogCommands(program2) {
108683
108513
  if (result.status === "pending") {
108684
108514
  const reason = result.last_result?.reason;
108685
108515
  const requiresNetworkAccess = isNetworkFailure(reason);
108686
- throw new ContextError(ExitCode.ExternalToolError, requiresNetworkAccess ? "runtime event delivery could not reach the configured sink" : "runtime event delivery was rejected by the configured sink", {
108516
+ throw new ContextError(ExitCode.ExternalToolError, requiresNetworkAccess ? "runtime event delivery could not reach the configured sink" : reason === "invalid_batch" ? "local telemetry bridge rejected the batch before network delivery; update the sink CLI for protocol compatibility" : "runtime event delivery was rejected by the configured sink", {
108687
108517
  category: ErrorCategory.ExternalToolFailed,
108688
108518
  reason_code: requiresNetworkAccess ? "runtime-events-network-unavailable" : "runtime-events-delivery-failed",
108689
108519
  pending_count: result.pending_count,
@@ -108712,10 +108542,10 @@ init_actionInputWorkspace();
108712
108542
 
108713
108543
  // src/project/actionCompletionOutput.ts
108714
108544
  init_atomicWrite();
108715
- var import_yaml43 = __toESM(require_dist(), 1);
108545
+ var import_yaml44 = __toESM(require_dist(), 1);
108716
108546
  import { Buffer as Buffer4 } from "node:buffer";
108717
- import { createHash as createHash29 } from "node:crypto";
108718
- import { join as join98 } from "node:path";
108547
+ import { createHash as createHash30 } from "node:crypto";
108548
+ import { join as join100 } from "node:path";
108719
108549
  var INLINE_LIMIT = 16 * 1024;
108720
108550
  function record4(value) {
108721
108551
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : undefined;
@@ -108728,7 +108558,7 @@ function shortText(value, limit = 400) {
108728
108558
  }
108729
108559
  function serializeActionCompletion(value, format2) {
108730
108560
  return format2 === "json" ? `${JSON.stringify(value, null, 2)}
108731
- ` : import_yaml43.default.stringify(value);
108561
+ ` : import_yaml44.default.stringify(value);
108732
108562
  }
108733
108563
  async function prepareActionCompletionOutput(input) {
108734
108564
  if (input.verbose)
@@ -108740,9 +108570,9 @@ async function prepareActionCompletionOutput(input) {
108740
108570
  const production = typeof result.stage_state === "string";
108741
108571
  if (production && Buffer4.byteLength(full) <= INLINE_LIMIT)
108742
108572
  return input.result;
108743
- const digest6 = createHash29("sha256").update(full).digest("hex");
108744
- const root2 = join98(input.projectRoot, ".tmp/context-runtime/action-results");
108745
- const resultFile = join98(root2, `${digest6}.json`);
108573
+ const digest6 = createHash30("sha256").update(full).digest("hex");
108574
+ const root2 = join100(input.projectRoot, ".tmp/context-runtime/action-results");
108575
+ const resultFile = join100(root2, `${digest6}.json`);
108746
108576
  await atomicWriteFile(resultFile, full);
108747
108577
  if (production)
108748
108578
  return {
@@ -108753,7 +108583,7 @@ async function prepareActionCompletionOutput(input) {
108753
108583
  ...pick(result, ["next", "next_preparation"])
108754
108584
  };
108755
108585
  const next2 = record4(result.next) ?? record4(record4(result.workflow)?.current) ?? record4(record4(result.continuation)?.next);
108756
- const nextFile = next2 === undefined ? undefined : join98(root2, `${digest6}.next.json`);
108586
+ const nextFile = next2 === undefined ? undefined : join100(root2, `${digest6}.next.json`);
108757
108587
  if (nextFile !== undefined)
108758
108588
  await atomicWriteFile(nextFile, serializeActionCompletion(next2, "json"));
108759
108589
  const outcomes = (Array.isArray(result.outcomes) ? result.outcomes : []).map(record4).filter((item) => item !== undefined);
@@ -108794,7 +108624,7 @@ async function prepareActionCompletionOutput(input) {
108794
108624
  ]),
108795
108625
  next_route: next2 === undefined ? null : {
108796
108626
  file: nextFile,
108797
- digest: `sha256:${createHash29("sha256").update(serializeActionCompletion(next2, "json")).digest("hex")}`,
108627
+ digest: `sha256:${createHash30("sha256").update(serializeActionCompletion(next2, "json")).digest("hex")}`,
108798
108628
  ...pick(next2, ["revision", "node", "availability"]),
108799
108629
  commands: next2.commands,
108800
108630
  gate: next2.gate === undefined ? undefined : pick(record4(next2.gate), ["id", "resolution", "delegatable"])
@@ -108841,7 +108671,7 @@ init_errors3();
108841
108671
  init_cliFeedback();
108842
108672
  init_exitCode();
108843
108673
  init_productionFeedback();
108844
- var import_yaml44 = __toESM(require_dist(), 1);
108674
+ var import_yaml45 = __toESM(require_dist(), 1);
108845
108675
  import { relative as relative24, resolve as resolve32 } from "node:path";
108846
108676
  async function completeCurrentProductionAction(input) {
108847
108677
  return withProductionFeedback({ operation: "action", file: input.submissionPath }, () => withProjectWriteLock(input.projectRoot, "production-action", async () => {
@@ -108860,7 +108690,7 @@ async function completeCurrentProductionAction(input) {
108860
108690
  throw invalid3("Production requires a stage-local manifest file, not stdin.");
108861
108691
  const path3 = relative24(resolve32(input.projectRoot, productionAgentDirectory(stage.id)), resolve32(input.cwd, input.submissionPath));
108862
108692
  const manifest = await readProductionFile({ projectRoot: input.projectRoot, stage: stage.id, path: path3 });
108863
- const value = import_yaml44.default.parse(manifest.text, { uniqueKeys: true });
108693
+ const value = import_yaml45.default.parse(manifest.text, { uniqueKeys: true });
108864
108694
  if (!value || typeof value !== "object" || !("stage" in value) || value.stage !== stage.id) {
108865
108695
  throw invalid3("The manifest must identify the current production stage. No tasks were saved.");
108866
108696
  }
@@ -109007,7 +108837,7 @@ init_errors3();
109007
108837
  init_cliFeedback();
109008
108838
  init_exitCode();
109009
108839
  init_productionFeedback();
109010
- var import_yaml45 = __toESM(require_dist(), 1);
108840
+ var import_yaml46 = __toESM(require_dist(), 1);
109011
108841
  import { relative as relative25, resolve as resolve33 } from "node:path";
109012
108842
  var productionKnownTasksSchema = productionPlanInputSchema.omit({ stage: true });
109013
108843
  async function prepareKnownProductionTasks(input) {
@@ -109022,7 +108852,7 @@ async function prepareKnownProductionTasks(input) {
109022
108852
  input_schema: zodToJsonSchema(productionKnownTasksSchema, { $refStrategy: "none" }),
109023
108853
  next_action: { command: "context status --format json" }
109024
108854
  });
109025
- const plan = productionKnownTasksSchema.parse(import_yaml45.default.parse(fixed2.text, { uniqueKeys: true }));
108855
+ const plan = productionKnownTasksSchema.parse(import_yaml46.default.parse(fixed2.text, { uniqueKeys: true }));
109026
108856
  const request = await productionPlanningRequest(input.projectRoot);
109027
108857
  let stage = await readProductionStage(input.projectRoot);
109028
108858
  if (!request || stage?.report_approved || (stage ? ![stage.id, request.revision].includes(input.revision) : request.revision !== input.revision)) {
@@ -109032,7 +108862,7 @@ async function prepareKnownProductionTasks(input) {
109032
108862
  await prepareCurrentProductionStage({ projectRoot: input.projectRoot, revision: input.revision });
109033
108863
  stage = await readProductionStage(input.projectRoot);
109034
108864
  }
109035
- const text10 = import_yaml45.default.stringify({ ...plan, stage: stage.id });
108865
+ const text10 = import_yaml46.default.stringify({ ...plan, stage: stage.id });
109036
108866
  const result = await submitProductionPlan({
109037
108867
  projectRoot: input.projectRoot,
109038
108868
  stage: stage.id,
@@ -109154,9 +108984,9 @@ function registerProjectActionCommands(program2) {
109154
108984
  // src/commands/cleanClaudePluginCache.ts
109155
108985
  init_cliFeedback();
109156
108986
  import { existsSync as existsSync27 } from "node:fs";
109157
- import { readdir as readdir25, rm as rm20 } from "node:fs/promises";
108987
+ import { readdir as readdir26, rm as rm20 } from "node:fs/promises";
109158
108988
  import { homedir } from "node:os";
109159
- import { join as join99 } from "node:path";
108989
+ import { join as join101 } from "node:path";
109160
108990
  var ORPHAN_MARKER = ".orphaned_at";
109161
108991
  var CLAUDE_PLUGIN_CACHE_ROOT_ENV = "C4A_CLAUDE_PLUGIN_CACHE_ROOT";
109162
108992
  var CLAUDE_PLUGIN_CACHE_HOME_ENV = "C4A_CLAUDE_PLUGIN_CACHE_HOME";
@@ -109169,23 +108999,23 @@ async function cleanClaudePluginCache(opts = {}) {
109169
108999
  lines.push("· claude plugin cache: missing — nothing to clean");
109170
109000
  return { lines, removed, scanned };
109171
109001
  }
109172
- const marketplaces = await readdir25(cacheRoot, { withFileTypes: true });
109002
+ const marketplaces = await readdir26(cacheRoot, { withFileTypes: true });
109173
109003
  for (const mp of marketplaces) {
109174
109004
  if (!mp.isDirectory())
109175
109005
  continue;
109176
- const mpDir = join99(cacheRoot, mp.name);
109177
- const plugins = await readdir25(mpDir, { withFileTypes: true });
109006
+ const mpDir = join101(cacheRoot, mp.name);
109007
+ const plugins = await readdir26(mpDir, { withFileTypes: true });
109178
109008
  for (const pl of plugins) {
109179
109009
  if (!pl.isDirectory())
109180
109010
  continue;
109181
- const plDir = join99(mpDir, pl.name);
109182
- const versions = await readdir25(plDir, { withFileTypes: true });
109011
+ const plDir = join101(mpDir, pl.name);
109012
+ const versions = await readdir26(plDir, { withFileTypes: true });
109183
109013
  for (const ver of versions) {
109184
109014
  if (!ver.isDirectory())
109185
109015
  continue;
109186
109016
  scanned += 1;
109187
- const verDir = join99(plDir, ver.name);
109188
- const markerPath = join99(verDir, ORPHAN_MARKER);
109017
+ const verDir = join101(plDir, ver.name);
109018
+ const markerPath = join101(verDir, ORPHAN_MARKER);
109189
109019
  if (!existsSync27(markerPath))
109190
109020
  continue;
109191
109021
  const label2 = `${mp.name}/${pl.name}/${ver.name}`;
@@ -109217,11 +109047,11 @@ function resolveClaudePluginCacheRoot(opts) {
109217
109047
  if (explicitRoot)
109218
109048
  return explicitRoot;
109219
109049
  const home = opts.home ?? process.env[CLAUDE_PLUGIN_CACHE_HOME_ENV] ?? homedir();
109220
- return join99(home, ".claude", "plugins", "cache");
109050
+ return join101(home, ".claude", "plugins", "cache");
109221
109051
  }
109222
109052
  async function isEmptyDir(dir) {
109223
109053
  try {
109224
- const entries2 = await readdir25(dir);
109054
+ const entries2 = await readdir26(dir);
109225
109055
  return entries2.length === 0;
109226
109056
  } catch {
109227
109057
  return false;
@@ -109249,13 +109079,13 @@ init_exitCode();
109249
109079
 
109250
109080
  // src/lib/packageVersion.ts
109251
109081
  import { existsSync as existsSync28, readFileSync as readFileSync9 } from "node:fs";
109252
- import { dirname as dirname42, join as join100 } from "node:path";
109082
+ import { dirname as dirname41, join as join102 } from "node:path";
109253
109083
  import { fileURLToPath as fileURLToPath7 } from "node:url";
109254
109084
  function readPackageVersion() {
109255
109085
  try {
109256
- let dir = dirname42(fileURLToPath7(import.meta.url));
109086
+ let dir = dirname41(fileURLToPath7(import.meta.url));
109257
109087
  for (let depth = 0;depth < 8; depth += 1) {
109258
- const packagePath = join100(dir, "package.json");
109088
+ const packagePath = join102(dir, "package.json");
109259
109089
  if (existsSync28(packagePath)) {
109260
109090
  const parsed = JSON.parse(readFileSync9(packagePath, "utf8"));
109261
109091
  if (typeof parsed.version === "string" && parsed.version.length > 0) {
@@ -109263,7 +109093,7 @@ function readPackageVersion() {
109263
109093
  }
109264
109094
  break;
109265
109095
  }
109266
- const parent = dirname42(dir);
109096
+ const parent = dirname41(dir);
109267
109097
  if (parent === dir)
109268
109098
  break;
109269
109099
  dir = parent;
@@ -109278,24 +109108,24 @@ function readPackageVersion() {
109278
109108
 
109279
109109
  // src/project/sourceCommands.ts
109280
109110
  init_src2();
109281
- import { readFile as readFile88 } from "node:fs/promises";
109111
+ import { readFile as readFile90 } from "node:fs/promises";
109282
109112
  import { isAbsolute as isAbsolute22, resolve as resolve37 } from "node:path";
109283
109113
  init_cliFeedback();
109284
109114
  init_errors3();
109285
109115
  init_exitCode();
109286
- var import_yaml49 = __toESM(require_dist(), 1);
109116
+ var import_yaml50 = __toESM(require_dist(), 1);
109287
109117
 
109288
109118
  // src/project/repoSourceRecovery.ts
109289
109119
  init_cliFeedback();
109290
109120
  init_errors3();
109291
109121
  init_exitCode();
109292
- import { execFile as execFile11 } from "node:child_process";
109122
+ import { execFile as execFile12 } from "node:child_process";
109293
109123
  import { existsSync as existsSync29 } from "node:fs";
109294
109124
  import { lstat as lstat12, mkdir as mkdir34, readlink as readlink2, realpath as realpath10, rm as rm21, symlink as symlink4 } from "node:fs/promises";
109295
- import { basename as basename11, dirname as dirname43, isAbsolute as isAbsolute19, relative as relative26, resolve as resolve34 } from "node:path";
109296
- import { promisify as promisify11 } from "node:util";
109125
+ import { basename as basename11, dirname as dirname42, isAbsolute as isAbsolute19, relative as relative26, resolve as resolve34 } from "node:path";
109126
+ import { promisify as promisify12 } from "node:util";
109297
109127
  init_writeLock();
109298
- var execFileAsync6 = promisify11(execFile11);
109128
+ var execFileAsync6 = promisify12(execFile12);
109299
109129
  var RECOVERY_SCHEMA = "context.repository-source-recovery.v1";
109300
109130
  function userInputError3(message, detail = {}) {
109301
109131
  return new ContextError(ExitCode.UserError, message, {
@@ -109477,7 +109307,7 @@ async function cloneCheckout(input) {
109477
109307
  next: `Use local mode with path ${JSON.stringify(target)} after inspecting the existing checkout.`
109478
109308
  });
109479
109309
  }
109480
- await mkdir34(dirname43(target), { recursive: true });
109310
+ await mkdir34(dirname42(target), { recursive: true });
109481
109311
  const cloneArgs = ["clone", "--no-checkout", "--depth=1", "--filter=blob:none", input.remote, target];
109482
109312
  try {
109483
109313
  await execFileAsync6("git", cloneArgs, { encoding: "utf8", maxBuffer: 4 * 1024 * 1024 });
@@ -109526,7 +109356,7 @@ async function bindLocalAlias(input) {
109526
109356
  const stats = await lstat12(alias).catch(() => null);
109527
109357
  if (stats !== null) {
109528
109358
  if (stats.isSymbolicLink()) {
109529
- const actual = resolve34(dirname43(alias), await readlink2(alias));
109359
+ const actual = resolve34(dirname42(alias), await readlink2(alias));
109530
109360
  const actualReal = await realpath10(actual).catch(() => null);
109531
109361
  if (actualReal !== null && actualReal === await realpath10(input.checkout))
109532
109362
  return;
@@ -109542,8 +109372,8 @@ async function bindLocalAlias(input) {
109542
109372
  });
109543
109373
  }
109544
109374
  }
109545
- await mkdir34(dirname43(alias), { recursive: true });
109546
- await symlink4(relative26(dirname43(alias), input.checkout) || ".", alias);
109375
+ await mkdir34(dirname42(alias), { recursive: true });
109376
+ await symlink4(relative26(dirname42(alias), input.checkout) || ".", alias);
109547
109377
  }
109548
109378
  function selectPhysicalGroup(sources, selector) {
109549
109379
  const direct = selectRepoSources(sources, selector);
@@ -109637,11 +109467,11 @@ async function restoreRepositorySources(input) {
109637
109467
  // src/project/sourceDocumentStatus.ts
109638
109468
  init_src2();
109639
109469
  import { existsSync as existsSync30 } from "node:fs";
109640
- import { readFile as readFile81 } from "node:fs/promises";
109641
- import { join as join102 } from "node:path";
109470
+ import { readFile as readFile83 } from "node:fs/promises";
109471
+ import { join as join104 } from "node:path";
109642
109472
  // src/project/sourceCommandViews.ts
109643
- import { readFile as readFile80 } from "node:fs/promises";
109644
- import { join as join101 } from "node:path";
109473
+ import { readFile as readFile82 } from "node:fs/promises";
109474
+ import { join as join103 } from "node:path";
109645
109475
  init_workspace();
109646
109476
  init_documentBatchManifest();
109647
109477
  function repoSourceAgentView(source2) {
@@ -109704,7 +109534,7 @@ async function fileSourceAgentViewWithNextAction(input) {
109704
109534
  };
109705
109535
  }
109706
109536
  function documentSourceManifestPath(source2) {
109707
- return source2.snapshot?.manifest ?? join101(source2.materializedAt, "manifest.json");
109537
+ return source2.snapshot?.manifest ?? join103(source2.materializedAt, "manifest.json");
109708
109538
  }
109709
109539
  async function fileSourceDocumentSiteHint(input) {
109710
109540
  const detection = await detectDocumentSiteFiles({
@@ -109714,7 +109544,7 @@ async function fileSourceDocumentSiteHint(input) {
109714
109544
  let snapshotConfigured = false;
109715
109545
  const manifest = documentSourceManifestPath(input.source);
109716
109546
  try {
109717
- const parsed = parseDocumentSnapshotForSource(JSON.parse(await readFile80(join101(input.projectRoot, manifest), "utf8")), input.source.name);
109547
+ const parsed = parseDocumentSnapshotForSource(JSON.parse(await readFile82(join103(input.projectRoot, manifest), "utf8")), input.source.name);
109718
109548
  snapshotConfigured = manifestUsesMdxJsonDocs(parsed);
109719
109549
  } catch {
109720
109550
  snapshotConfigured = false;
@@ -109746,11 +109576,11 @@ async function larkSourceAgentViewWithNextAction(input) {
109746
109576
  // src/project/sourceDocumentStatus.ts
109747
109577
  init_documentBatchManifest();
109748
109578
  function documentSourceManifestPath2(source2) {
109749
- return source2.snapshot?.manifest ?? join102(source2.materializedAt, "manifest.json");
109579
+ return source2.snapshot?.manifest ?? join104(source2.materializedAt, "manifest.json");
109750
109580
  }
109751
109581
  async function documentSnapshotState(input) {
109752
109582
  const manifest = documentSourceManifestPath2(input.source);
109753
- const manifestPath = join102(input.projectRoot, manifest);
109583
+ const manifestPath = join104(input.projectRoot, manifest);
109754
109584
  if (!existsSync30(manifestPath)) {
109755
109585
  return {
109756
109586
  snapshotReady: false,
@@ -109761,7 +109591,7 @@ async function documentSnapshotState(input) {
109761
109591
  };
109762
109592
  }
109763
109593
  try {
109764
- const parsed = findDocumentSnapshotForSource(JSON.parse(await readFile81(manifestPath, "utf8")), input.source.name);
109594
+ const parsed = findDocumentSnapshotForSource(JSON.parse(await readFile83(manifestPath, "utf8")), input.source.name);
109765
109595
  if (parsed === null) {
109766
109596
  return {
109767
109597
  snapshotReady: false,
@@ -109829,7 +109659,7 @@ async function documentSnapshotState(input) {
109829
109659
  const missing = [
109830
109660
  ...parsed.files.map((file) => file.path),
109831
109661
  ...(parsed.assets ?? []).filter((asset) => asset.content_hash !== undefined).map((asset) => asset.path)
109832
- ].find((path3) => !existsSync30(join102(input.projectRoot, input.source.materializedAt, path3)));
109662
+ ].find((path3) => !existsSync30(join104(input.projectRoot, input.source.materializedAt, path3)));
109833
109663
  if (missing !== undefined) {
109834
109664
  return {
109835
109665
  snapshotReady: false,
@@ -109928,10 +109758,10 @@ init_atomicWrite();
109928
109758
  init_cliFeedback();
109929
109759
  init_errors3();
109930
109760
  init_exitCode();
109931
- var import_yaml46 = __toESM(require_dist(), 1);
109932
- import { createHash as createHash30 } from "node:crypto";
109933
- import { readFile as readFile82, realpath as realpath11 } from "node:fs/promises";
109934
- import { basename as basename12, extname as extname15, isAbsolute as isAbsolute20, join as join103, relative as relative27, resolve as resolve35 } from "node:path";
109761
+ var import_yaml47 = __toESM(require_dist(), 1);
109762
+ import { createHash as createHash31 } from "node:crypto";
109763
+ import { readFile as readFile84, realpath as realpath11 } from "node:fs/promises";
109764
+ import { basename as basename12, extname as extname15, isAbsolute as isAbsolute20, join as join105, relative as relative27, resolve as resolve35 } from "node:path";
109935
109765
  init_writeLock();
109936
109766
  var SOURCE_NAME_PATTERN3 = /^[a-z0-9][a-z0-9._-]*$/u;
109937
109767
  function isDateSourceNamespace(value) {
@@ -109960,7 +109790,7 @@ function defaultLarkModule(input) {
109960
109790
  if (titleSlug.length > 0)
109961
109791
  return titleSlug;
109962
109792
  }
109963
- const opaqueSlug = (kind, identity) => `${kind}-${createHash30("sha256").update(identity).digest("hex").slice(0, 12)}`;
109793
+ const opaqueSlug = (kind, identity) => `${kind}-${createHash31("sha256").update(identity).digest("hex").slice(0, 12)}`;
109964
109794
  if (input.url !== undefined) {
109965
109795
  try {
109966
109796
  const parsed = new URL(input.url);
@@ -110027,8 +109857,8 @@ function assertSafeFileInclude(value) {
110027
109857
  }
110028
109858
  async function readRegistryDocument(projectRoot, registryPath2) {
110029
109859
  try {
110030
- const content3 = await readFile82(join103(projectRoot, registryPath2), "utf8");
110031
- return content3.trim().length === 0 ? { sources: [] } : import_yaml46.default.parse(content3);
109860
+ const content3 = await readFile84(join105(projectRoot, registryPath2), "utf8");
109861
+ return content3.trim().length === 0 ? { sources: [] } : import_yaml47.default.parse(content3);
110032
109862
  } catch (error) {
110033
109863
  if (error instanceof Error && "code" in error && error.code === "ENOENT")
110034
109864
  return { sources: [] };
@@ -110145,7 +109975,7 @@ async function addFileSourceUnlocked(input) {
110145
109975
  const record6 = entry2;
110146
109976
  return record6.name !== input.name && record6.id !== input.name;
110147
109977
  }), nextEntry];
110148
- await atomicWriteFile(join103(input.projectRoot, DEFAULT_FILE_SOURCES_REGISTRY_PATH), import_yaml46.default.stringify({ sources: nextSources }));
109978
+ await atomicWriteFile(join105(input.projectRoot, DEFAULT_FILE_SOURCES_REGISTRY_PATH), import_yaml47.default.stringify({ sources: nextSources }));
110149
109979
  const updated = await loadSourcesRegistry({ rootDir: input.projectRoot });
110150
109980
  const entry = updated.files.find((source2) => source2.name === input.name || source2.id === input.name);
110151
109981
  if (entry === undefined) {
@@ -110201,7 +110031,7 @@ async function addLarkSourceUnlocked(input) {
110201
110031
  const record6 = entry2;
110202
110032
  return record6.name !== input.name && record6.id !== input.name;
110203
110033
  }), nextEntry];
110204
- await atomicWriteFile(join103(input.projectRoot, DEFAULT_LARK_SOURCES_REGISTRY_PATH), import_yaml46.default.stringify({ sources: nextSources }));
110034
+ await atomicWriteFile(join105(input.projectRoot, DEFAULT_LARK_SOURCES_REGISTRY_PATH), import_yaml47.default.stringify({ sources: nextSources }));
110205
110035
  const updated = await loadSourcesRegistry({ rootDir: input.projectRoot });
110206
110036
  const entry = updated.larks.find((source2) => source2.name === input.name || source2.id === input.name);
110207
110037
  if (entry === undefined) {
@@ -110307,12 +110137,12 @@ function parseBatchItem(value, index2) {
110307
110137
  const url = optionalString(record6, "url", path3);
110308
110138
  const docToken = optionalString(record6, "docToken", path3);
110309
110139
  const wikiToken = optionalString(record6, "wikiToken", path3);
110310
- const title = optionalString(record6, "title", path3);
110140
+ const title2 = optionalString(record6, "title", path3);
110311
110141
  const module = optionalString(record6, "module", path3) ?? defaultLarkModule({
110312
110142
  ...url !== undefined ? { url } : {},
110313
110143
  ...docToken !== undefined ? { docToken } : {},
110314
110144
  ...wikiToken !== undefined ? { wikiToken } : {},
110315
- ...title !== undefined ? { title } : {}
110145
+ ...title2 !== undefined ? { title: title2 } : {}
110316
110146
  });
110317
110147
  return {
110318
110148
  type,
@@ -110320,7 +110150,7 @@ function parseBatchItem(value, index2) {
110320
110150
  ...url !== undefined ? { url } : {},
110321
110151
  ...docToken !== undefined ? { docToken } : {},
110322
110152
  ...wikiToken !== undefined ? { wikiToken } : {},
110323
- ...title !== undefined ? { title } : {}
110153
+ ...title2 !== undefined ? { title: title2 } : {}
110324
110154
  };
110325
110155
  }
110326
110156
  function parseBatchPayload(payload) {
@@ -110401,11 +110231,11 @@ init_candidateLedger();
110401
110231
  init_documentBatchManifest();
110402
110232
  init_workspace();
110403
110233
  init_writeLock();
110404
- var import_yaml47 = __toESM(require_dist(), 1);
110234
+ var import_yaml48 = __toESM(require_dist(), 1);
110405
110235
  import { existsSync as existsSync31 } from "node:fs";
110406
- import { createHash as createHash31 } from "node:crypto";
110407
- import { readFile as readFile83, readdir as readdir26, rm as rm22 } from "node:fs/promises";
110408
- import { isAbsolute as isAbsolute21, join as join104, relative as relative28, resolve as resolve36, sep as sep8 } from "node:path";
110236
+ import { createHash as createHash32 } from "node:crypto";
110237
+ import { readFile as readFile85, readdir as readdir27, rm as rm22 } from "node:fs/promises";
110238
+ import { isAbsolute as isAbsolute21, join as join106, relative as relative28, resolve as resolve36, sep as sep8 } from "node:path";
110409
110239
  function sourceIdentity(source2) {
110410
110240
  if (source2.kind === "source.collection")
110411
110241
  return;
@@ -110437,10 +110267,10 @@ function collectStrings(value, output) {
110437
110267
  }
110438
110268
  }
110439
110269
  async function yamlReferences(input) {
110440
- const absolutePath = join104(input.projectRoot, input.path);
110270
+ const absolutePath = join106(input.projectRoot, input.path);
110441
110271
  if (!existsSync31(absolutePath))
110442
110272
  return false;
110443
- const parsed = import_yaml47.default.parse(await readFile83(absolutePath, "utf8"));
110273
+ const parsed = import_yaml48.default.parse(await readFile85(absolutePath, "utf8"));
110444
110274
  const strings = [];
110445
110275
  collectStrings(parsed, strings);
110446
110276
  return strings.some((value) => stringReferencesSource(value, input.source));
@@ -110566,11 +110396,11 @@ async function registryRemovalWrite(projectRoot, source2) {
110566
110396
  const path3 = registryPath2(source2.type);
110567
110397
  if (path3 === null)
110568
110398
  return;
110569
- const absolutePath = join104(projectRoot, path3);
110570
- const document4 = existsSync31(absolutePath) ? import_yaml47.default.parse(await readFile83(absolutePath, "utf8")) : { sources: [] };
110399
+ const absolutePath = join106(projectRoot, path3);
110400
+ const document4 = existsSync31(absolutePath) ? import_yaml48.default.parse(await readFile85(absolutePath, "utf8")) : { sources: [] };
110571
110401
  return {
110572
110402
  path: absolutePath,
110573
- bytes: import_yaml47.default.stringify(removeDocumentEntry(document4, source2))
110403
+ bytes: import_yaml48.default.stringify(removeDocumentEntry(document4, source2))
110574
110404
  };
110575
110405
  }
110576
110406
  function safeManagedMaterializedPath(projectRoot, source2) {
@@ -110585,7 +110415,7 @@ function safeManagedMaterializedPath(projectRoot, source2) {
110585
110415
  return absolute;
110586
110416
  }
110587
110417
  function safeManagedManifestPath(projectRoot, source2) {
110588
- const manifest = source2.manifest ?? join104(source2.materializedAt, "manifest.json");
110418
+ const manifest = source2.manifest ?? join106(source2.materializedAt, "manifest.json");
110589
110419
  if (isAbsolute21(manifest))
110590
110420
  throw unsafeOwnership(source2, manifest);
110591
110421
  const absolute = resolve36(projectRoot, manifest);
@@ -110620,7 +110450,7 @@ function projectRelative(projectRoot, path3) {
110620
110450
  return relative28(projectRoot, path3).split(sep8).join("/");
110621
110451
  }
110622
110452
  function digest6(value) {
110623
- return `sha256:${createHash31("sha256").update(JSON.stringify(value)).digest("hex")}`;
110453
+ return `sha256:${createHash32("sha256").update(JSON.stringify(value)).digest("hex")}`;
110624
110454
  }
110625
110455
  async function sharedMaterializedOwners(projectRoot, source2) {
110626
110456
  const target = safeManagedMaterializedPath(projectRoot, source2);
@@ -110714,7 +110544,7 @@ async function createRemovalPlan(projectRoot, selector) {
110714
110544
  source: source2,
110715
110545
  registry: registryPath2(source2.type),
110716
110546
  registryBytes: registryWrite?.bytes ?? null,
110717
- managedBytes: source2.type === "note" || source2.type === "sessions" ? await readFile83(absoluteRemovals[0], "utf8") : null,
110547
+ managedBytes: source2.type === "note" || source2.type === "sessions" ? await readFile85(absoluteRemovals[0], "utf8") : null,
110718
110548
  references,
110719
110549
  cleanup,
110720
110550
  manifestBytes: manifestWrite?.bytes ?? null
@@ -110747,10 +110577,10 @@ function publicRemovalResult(plan, action) {
110747
110577
  };
110748
110578
  }
110749
110579
  async function pruneExtractRuntime(projectRoot, source2) {
110750
- const fingerprintPath = join104(projectRoot, ".tmp/context-runtime/extract/source-fingerprints.json");
110580
+ const fingerprintPath = join106(projectRoot, ".tmp/context-runtime/extract/source-fingerprints.json");
110751
110581
  const removedPhaseIds = new Set;
110752
110582
  if (existsSync31(fingerprintPath)) {
110753
- const parsed = JSON.parse(await readFile83(fingerprintPath, "utf8"));
110583
+ const parsed = JSON.parse(await readFile85(fingerprintPath, "utf8"));
110754
110584
  const phases = parsed.phases ?? {};
110755
110585
  const next2 = Object.fromEntries(Object.entries(phases).filter(([phaseId, raw]) => {
110756
110586
  if (raw === null || typeof raw !== "object" || Array.isArray(raw))
@@ -110764,27 +110594,27 @@ async function pruneExtractRuntime(projectRoot, source2) {
110764
110594
  await atomicWriteFile(fingerprintPath, `${JSON.stringify({ ...parsed, phases: next2 }, null, 2)}
110765
110595
  `);
110766
110596
  }
110767
- const phaseOwnershipPath = join104(projectRoot, ".tmp/context-runtime/extract/custom-phase-candidates.json");
110597
+ const phaseOwnershipPath = join106(projectRoot, ".tmp/context-runtime/extract/custom-phase-candidates.json");
110768
110598
  if (existsSync31(phaseOwnershipPath) && removedPhaseIds.size > 0) {
110769
- const parsed = JSON.parse(await readFile83(phaseOwnershipPath, "utf8"));
110599
+ const parsed = JSON.parse(await readFile85(phaseOwnershipPath, "utf8"));
110770
110600
  const phases = Object.fromEntries(Object.entries(parsed.phases ?? {}).filter(([phaseId]) => !removedPhaseIds.has(phaseId)));
110771
110601
  await atomicWriteFile(phaseOwnershipPath, `${JSON.stringify({ ...parsed, phases }, null, 2)}
110772
110602
  `);
110773
110603
  }
110774
- const symbolPath = join104(projectRoot, ".tmp/context-runtime/extract/source-symbols.json");
110604
+ const symbolPath = join106(projectRoot, ".tmp/context-runtime/extract/source-symbols.json");
110775
110605
  if (existsSync31(symbolPath)) {
110776
- const parsed = JSON.parse(await readFile83(symbolPath, "utf8"));
110606
+ const parsed = JSON.parse(await readFile85(symbolPath, "utf8"));
110777
110607
  const symbols = Array.isArray(parsed.symbols) ? parsed.symbols.filter((entry) => entry === null || typeof entry !== "object" || Array.isArray(entry) || entry.source !== source2.name) : [];
110778
110608
  const phaseFingerprints = Object.fromEntries(Object.entries(parsed.phaseFingerprints ?? {}).filter(([phaseId]) => !removedPhaseIds.has(phaseId)));
110779
110609
  await atomicWriteFile(symbolPath, `${JSON.stringify({ ...parsed, phaseFingerprints, symbols }, null, 2)}
110780
110610
  `);
110781
110611
  }
110782
- const snapshotRoot = join104(projectRoot, ".tmp/context-runtime/extract/candidates");
110612
+ const snapshotRoot = join106(projectRoot, ".tmp/context-runtime/extract/candidates");
110783
110613
  const visit4 = async (directory) => {
110784
110614
  if (!existsSync31(directory))
110785
110615
  return;
110786
- for (const entry of await readdir26(directory, { withFileTypes: true })) {
110787
- const path3 = join104(directory, entry.name);
110616
+ for (const entry of await readdir27(directory, { withFileTypes: true })) {
110617
+ const path3 = join106(directory, entry.name);
110788
110618
  if (entry.isDirectory()) {
110789
110619
  await visit4(path3);
110790
110620
  continue;
@@ -110792,7 +110622,7 @@ async function pruneExtractRuntime(projectRoot, source2) {
110792
110622
  if (!entry.isFile() || !entry.name.endsWith(".json"))
110793
110623
  continue;
110794
110624
  try {
110795
- const parsed = JSON.parse(await readFile83(path3, "utf8"));
110625
+ const parsed = JSON.parse(await readFile85(path3, "utf8"));
110796
110626
  const refs = Array.isArray(parsed.source_refs) ? parsed.source_refs : [];
110797
110627
  if (parsed.source === source2.name || refs.some((ref2) => typeof ref2 === "string" && stringReferencesSource(ref2, source2))) {
110798
110628
  await rm22(path3, { force: true });
@@ -110834,7 +110664,7 @@ async function removeProjectSource(input) {
110834
110664
  });
110835
110665
  }
110836
110666
  await applyAtomicFileBatch({
110837
- transactionRoot: join104(input.projectRoot, ".tmp", "context-runtime", "source-remove-transactions"),
110667
+ transactionRoot: join106(input.projectRoot, ".tmp", "context-runtime", "source-remove-transactions"),
110838
110668
  writes: [...plan.registryWrite === undefined ? [] : [plan.registryWrite], ...plan.manifestWrite !== undefined ? [plan.manifestWrite] : []],
110839
110669
  removals: plan.absoluteRemovals
110840
110670
  });
@@ -110850,7 +110680,7 @@ init_workspace();
110850
110680
  init_writeLock();
110851
110681
  init_durableMultiFileTransaction();
110852
110682
  init_durableSingleFileTransaction();
110853
- import { readFile as readFile84 } from "node:fs/promises";
110683
+ import { readFile as readFile86 } from "node:fs/promises";
110854
110684
  import ts from "typescript";
110855
110685
  function generateSourceConfiguration(text10, selected) {
110856
110686
  const file = ts.createSourceFile("index.ts", text10, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
@@ -110990,7 +110820,7 @@ function generateSourceConfiguration(text10, selected) {
110990
110820
  async function configureRegisteredSources(projectRoot, selected) {
110991
110821
  return withProjectWriteLock(projectRoot, "source-project-configuration", async () => {
110992
110822
  const path3 = "src/index.ts";
110993
- const config = JSON.parse(await readFile84(await safeProjectTarget(projectRoot, "package.json"), "utf8"));
110823
+ const config = JSON.parse(await readFile86(await safeProjectTarget(projectRoot, "package.json"), "utf8"));
110994
110824
  if (config.context?.entry !== path3)
110995
110825
  return {
110996
110826
  status: "manual",
@@ -110999,7 +110829,7 @@ async function configureRegisteredSources(projectRoot, selected) {
110999
110829
  sources: selected
111000
110830
  };
111001
110831
  const target = await safeProjectTarget(projectRoot, path3);
111002
- const text10 = await readFile84(target, "utf8");
110832
+ const text10 = await readFile86(target, "utf8");
111003
110833
  const updated = generateSourceConfiguration(text10, selected);
111004
110834
  if (updated === undefined)
111005
110835
  return {
@@ -111079,7 +110909,7 @@ async function readIncludeList(projectRoot, path3) {
111079
110909
  }
111080
110910
  let content3;
111081
110911
  try {
111082
- content3 = await readFile88(isAbsolute22(trimmed) ? resolve37(trimmed) : resolve37(projectRoot, trimmed), "utf8");
110912
+ content3 = await readFile90(isAbsolute22(trimmed) ? resolve37(trimmed) : resolve37(projectRoot, trimmed), "utf8");
111083
110913
  } catch (error) {
111084
110914
  throw new ContextError(ExitCode.UserError, `cannot read include list: ${trimmed}`, {
111085
110915
  category: ErrorCategory.UserInputInvalid,
@@ -111147,7 +110977,7 @@ function writeFormatted(value, format2) {
111147
110977
  return;
111148
110978
  }
111149
110979
  if (format2 === "yaml") {
111150
- process.stdout.write(import_yaml49.default.stringify(value));
110980
+ process.stdout.write(import_yaml50.default.stringify(value));
111151
110981
  return;
111152
110982
  }
111153
110983
  process.stdout.write(renderTable2(value));
@@ -111337,14 +111167,14 @@ report shown above. A direct maintenance call outside that Route may omit it.
111337
111167
  const url = optionalString2(options.url);
111338
111168
  const docToken = optionalString2(options.docToken);
111339
111169
  const wikiToken = optionalString2(options.wikiToken);
111340
- const title = optionalString2(options.title);
111170
+ const title2 = optionalString2(options.title);
111341
111171
  const requestedModule = optionalString2(options.module);
111342
111172
  const batchMode = sourceNamespace.generated || isDateSourceNamespace(sourceNamespace.name) || requestedModule !== undefined;
111343
111173
  const module = batchMode ? requestedModule ?? defaultLarkModule({
111344
111174
  ...url !== undefined ? { url } : {},
111345
111175
  ...docToken !== undefined ? { docToken } : {},
111346
111176
  ...wikiToken !== undefined ? { wikiToken } : {},
111347
- ...title !== undefined ? { title } : {}
111177
+ ...title2 !== undefined ? { title: title2 } : {}
111348
111178
  }) : undefined;
111349
111179
  const sourceName = module === undefined ? sourceNamespace.name : `${sourceNamespace.name}/${module}`;
111350
111180
  const result = await addLarkSource({
@@ -111354,7 +111184,7 @@ report shown above. A direct maintenance call outside that Route may omit it.
111354
111184
  ...url !== undefined ? { url } : {},
111355
111185
  ...docToken !== undefined ? { docToken } : {},
111356
111186
  ...wikiToken !== undefined ? { wikiToken } : {},
111357
- ...title !== undefined ? { title } : {}
111187
+ ...title2 !== undefined ? { title: title2 } : {}
111358
111188
  });
111359
111189
  writeFormatted(options.configure ? { ...result, configuration: await configureRegisteredSources(projectRoot, [{ type: "lark", name: sourceName }]) } : result, format2);
111360
111190
  });
@@ -111468,19 +111298,19 @@ report shown above. A direct maintenance call outside that Route may omit it.
111468
111298
  init_cliFeedback();
111469
111299
  init_errors3();
111470
111300
  init_exitCode();
111471
- var import_yaml51 = __toESM(require_dist(), 1);
111301
+ var import_yaml52 = __toESM(require_dist(), 1);
111472
111302
 
111473
111303
  // src/project/productionSkillCatalog.ts
111474
111304
  init_errors3();
111475
111305
  init_cliFeedback();
111476
111306
  init_exitCode();
111477
- var import_yaml50 = __toESM(require_dist(), 1);
111307
+ var import_yaml51 = __toESM(require_dist(), 1);
111478
111308
  import { constants as constants6, existsSync as existsSync32 } from "node:fs";
111479
- import { lstat as lstat13, open as open5, readdir as readdir27 } from "node:fs/promises";
111480
- import { dirname as dirname44, join as join106, resolve as resolve38 } from "node:path";
111309
+ import { lstat as lstat13, open as open5, readdir as readdir28 } from "node:fs/promises";
111310
+ import { dirname as dirname43, join as join108, resolve as resolve38 } from "node:path";
111481
111311
  import { fileURLToPath as fileURLToPath8 } from "node:url";
111482
111312
  function bundledSkillRoot() {
111483
- const directory = dirname44(fileURLToPath8(import.meta.url));
111313
+ const directory = dirname43(fileURLToPath8(import.meta.url));
111484
111314
  const root2 = [resolve38(directory, "indexers/bundles"), resolve38(directory, "../../dist/indexers/bundles")].find((path3) => existsSync32(path3));
111485
111315
  if (!root2)
111486
111316
  throw new TypeError("Bundled skill files are unavailable; build or reinstall the Context CLI.");
@@ -111504,10 +111334,10 @@ async function listProductionSkills(root2) {
111504
111334
  }
111505
111335
  }
111506
111336
  async function readProductionSkills(root2) {
111507
- const entries2 = (await readdir27(root2, { withFileTypes: true })).filter((entry) => entry.isDirectory()).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
111337
+ const entries2 = (await readdir28(root2, { withFileTypes: true })).filter((entry) => entry.isDirectory()).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
111508
111338
  const skills = [];
111509
111339
  for (const directory of entries2) {
111510
- const entry = join106(root2, directory.name, "SKILL.md");
111340
+ const entry = join108(root2, directory.name, "SKILL.md");
111511
111341
  if (!(await lstat13(entry)).isFile())
111512
111342
  throw new TypeError(`Skill entry must be a regular file: ${entry}`);
111513
111343
  const handle2 = await open5(entry, constants6.O_RDONLY | constants6.O_NOFOLLOW | constants6.O_NONBLOCK);
@@ -111524,7 +111354,7 @@ async function readProductionSkills(root2) {
111524
111354
  const frontmatter2 = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/u.exec(header);
111525
111355
  if (!frontmatter2)
111526
111356
  throw new TypeError(`Skill entry needs complete frontmatter within 64 KiB: ${entry}`);
111527
- const value = import_yaml50.default.parse(frontmatter2[1], { uniqueKeys: true });
111357
+ const value = import_yaml51.default.parse(frontmatter2[1], { uniqueKeys: true });
111528
111358
  if (!value || typeof value !== "object" || !("name" in value) || typeof value.name !== "string" || !value.name.trim() || !("description" in value) || typeof value.description !== "string" || !value.description.trim()) {
111529
111359
  throw new TypeError(`Skill entry needs a name and description: ${entry}`);
111530
111360
  }
@@ -111587,7 +111417,7 @@ function outputFormat2(options) {
111587
111417
  }
111588
111418
  function writeOutput(value, format2) {
111589
111419
  process.stdout.write(format2 === "json" ? `${JSON.stringify(value, null, 2)}
111590
- ` : import_yaml51.default.stringify(value));
111420
+ ` : import_yaml52.default.stringify(value));
111591
111421
  }
111592
111422
  function registerProjectIndexerCommands(program2) {
111593
111423
  const indexer = program2.command("indexer").description("Discover bundled skills or inspect a benchmark result");
@@ -111642,7 +111472,7 @@ init_exitCode();
111642
111472
  init_workflowProvider();
111643
111473
  init_workspace();
111644
111474
  import { existsSync as existsSync33 } from "node:fs";
111645
- import { dirname as dirname45, resolve as resolve39 } from "node:path";
111475
+ import { dirname as dirname44, resolve as resolve39 } from "node:path";
111646
111476
  function shellQuote7(value) {
111647
111477
  return /^[A-Za-z0-9._/=-]+$/u.test(value) ? value : `'${value.replaceAll("'", `'"'"'`)}'`;
111648
111478
  }
@@ -111675,10 +111505,10 @@ function readyResult(input, projectRoot, relocation) {
111675
111505
  return {
111676
111506
  schema: "context.entry.v1",
111677
111507
  guidance: {
111678
- knowledge_updates: { path: resolve39(dirname45(contextWorkflowProviderPath()), "resources/procedures/knowledge-updates.md") },
111679
- workspace_prepare: { path: resolve39(dirname45(contextWorkflowProviderPath()), "resources/procedures/workspace-prepare.md") },
111680
- workspace_commit: { path: resolve39(dirname45(contextWorkflowProviderPath()), "resources/procedures/workspace-commit.md") },
111681
- workspace_restore: { path: resolve39(dirname45(contextWorkflowProviderPath()), "resources/procedures/workspace-restore.md") }
111508
+ knowledge_updates: { path: resolve39(dirname44(contextWorkflowProviderPath()), "resources/procedures/knowledge-updates.md") },
111509
+ workspace_prepare: { path: resolve39(dirname44(contextWorkflowProviderPath()), "resources/procedures/workspace-prepare.md") },
111510
+ workspace_commit: { path: resolve39(dirname44(contextWorkflowProviderPath()), "resources/procedures/workspace-commit.md") },
111511
+ workspace_restore: { path: resolve39(dirname44(contextWorkflowProviderPath()), "resources/procedures/workspace-restore.md") }
111682
111512
  },
111683
111513
  state: relocation ? "workspace-relocation-required" : "workspace-ready",
111684
111514
  cwd: resolve39(input.cwd),
@@ -111915,20 +111745,20 @@ init_cliFeedback();
111915
111745
  init_errors3();
111916
111746
  init_exitCode();
111917
111747
  import { existsSync as existsSync35 } from "node:fs";
111918
- import { dirname as dirname47, join as join108, resolve as resolve40 } from "node:path";
111748
+ import { dirname as dirname46, join as join110, resolve as resolve40 } from "node:path";
111919
111749
  import { fileURLToPath as fileURLToPath9 } from "node:url";
111920
111750
 
111921
111751
  // src/project/pluginInstallTargets.ts
111922
111752
  init_cliFeedback();
111923
111753
  init_errors3();
111924
111754
  init_exitCode();
111925
- import { execFile as execFile12 } from "node:child_process";
111755
+ import { execFile as execFile13 } from "node:child_process";
111926
111756
  import { existsSync as existsSync34 } from "node:fs";
111927
- import { cp as cp2, mkdir as mkdir35, readdir as readdir28, readFile as readFile89, rename as rename9, rm as rm23, writeFile as writeFile27 } from "node:fs/promises";
111757
+ import { cp as cp2, mkdir as mkdir35, readdir as readdir29, readFile as readFile91, rename as rename9, rm as rm23, writeFile as writeFile27 } from "node:fs/promises";
111928
111758
  import { homedir as homedir2 } from "node:os";
111929
- import { dirname as dirname46, join as join107 } from "node:path";
111930
- import { promisify as promisify12 } from "node:util";
111931
- var execFileAsync7 = promisify12(execFile12);
111759
+ import { dirname as dirname45, join as join109 } from "node:path";
111760
+ import { promisify as promisify13 } from "node:util";
111761
+ var execFileAsync7 = promisify13(execFile13);
111932
111762
  var MARKETPLACE_NAME = "c4a";
111933
111763
  var PLUGIN_ID = "c4a@c4a";
111934
111764
  var PLUGIN_NAME = "c4a";
@@ -111980,23 +111810,23 @@ async function claudePluginInstalled(pluginId) {
111980
111810
  }
111981
111811
  }
111982
111812
  function codexHome() {
111983
- return process.env.CODEX_HOME?.trim() || join107(homedir2(), ".codex");
111813
+ return process.env.CODEX_HOME?.trim() || join109(homedir2(), ".codex");
111984
111814
  }
111985
111815
  function claudePluginCacheRoot() {
111986
111816
  const explicitRoot = process.env[CLAUDE_PLUGIN_CACHE_ROOT_ENV2]?.trim();
111987
111817
  if (explicitRoot)
111988
111818
  return explicitRoot;
111989
111819
  const home = process.env[CLAUDE_PLUGIN_CACHE_HOME_ENV2]?.trim() || homedir2();
111990
- return join107(home, ".claude", "plugins", "cache");
111820
+ return join109(home, ".claude", "plugins", "cache");
111991
111821
  }
111992
111822
  function sharedSkillsRoot() {
111993
- return process.env[SHARED_SKILLS_ROOT_ENV]?.trim() || join107(homedir2(), ".agents", "skills");
111823
+ return process.env[SHARED_SKILLS_ROOT_ENV]?.trim() || join109(homedir2(), ".agents", "skills");
111994
111824
  }
111995
111825
  function claudeSkillsRoot() {
111996
- return process.env[CLAUDE_SKILLS_ROOT_ENV]?.trim() || join107(homedir2(), ".claude", "skills");
111826
+ return process.env[CLAUDE_SKILLS_ROOT_ENV]?.trim() || join109(homedir2(), ".claude", "skills");
111997
111827
  }
111998
111828
  function cursorPluginRoot() {
111999
- return process.env[CURSOR_PLUGIN_ROOT_ENV]?.trim() || join107(homedir2(), ".cursor", "plugins", "local", PLUGIN_NAME);
111829
+ return process.env[CURSOR_PLUGIN_ROOT_ENV]?.trim() || join109(homedir2(), ".cursor", "plugins", "local", PLUGIN_NAME);
112000
111830
  }
112001
111831
  function blockHeader(line) {
112002
111832
  const match = line.match(/^\s*\[([^\]]+)\]\s*$/u);
@@ -112047,8 +111877,8 @@ function pruneLegacyCodexConfigContent(content3) {
112047
111877
  `), removed };
112048
111878
  }
112049
111879
  async function pruneLegacyCodexConfig(dryRun, steps) {
112050
- const configPath = join107(codexHome(), "config.toml");
112051
- const current2 = await readFile89(configPath, "utf8").catch(() => "");
111880
+ const configPath = join109(codexHome(), "config.toml");
111881
+ const current2 = await readFile91(configPath, "utf8").catch(() => "");
112052
111882
  if (!current2)
112053
111883
  return;
112054
111884
  const next2 = pruneLegacyCodexConfigContent(current2);
@@ -112064,10 +111894,10 @@ async function pruneLegacyCodexConfig(dryRun, steps) {
112064
111894
  }
112065
111895
  }
112066
111896
  async function pruneCodexPluginCacheForName(pluginName, keepVersion, dryRun, steps) {
112067
- const cacheRoot = join107(codexHome(), "plugins", "cache", MARKETPLACE_NAME, pluginName);
111897
+ const cacheRoot = join109(codexHome(), "plugins", "cache", MARKETPLACE_NAME, pluginName);
112068
111898
  if (!existsSync34(cacheRoot))
112069
111899
  return;
112070
- const versions = (await readdir28(cacheRoot, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory() && entry.name !== keepVersion).map((entry) => entry.name).sort();
111900
+ const versions = (await readdir29(cacheRoot, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory() && entry.name !== keepVersion).map((entry) => entry.name).sort();
112071
111901
  if (versions.length === 0)
112072
111902
  return;
112073
111903
  steps.push({
@@ -112076,7 +111906,7 @@ async function pruneCodexPluginCacheForName(pluginName, keepVersion, dryRun, ste
112076
111906
  status: dryRun ? "planned" : "ran"
112077
111907
  });
112078
111908
  if (!dryRun)
112079
- await Promise.all(versions.map((version3) => rm23(join107(cacheRoot, version3), { recursive: true, force: true })));
111909
+ await Promise.all(versions.map((version3) => rm23(join109(cacheRoot, version3), { recursive: true, force: true })));
112080
111910
  }
112081
111911
  async function pruneCodexPluginCache(currentVersion, dryRun, steps) {
112082
111912
  await pruneCodexPluginCacheForName(PLUGIN_NAME, currentVersion, dryRun, steps);
@@ -112086,7 +111916,7 @@ async function pruneCodexPluginCache(currentVersion, dryRun, steps) {
112086
111916
  }
112087
111917
  async function isEmptyDir2(dir) {
112088
111918
  try {
112089
- return (await readdir28(dir)).length === 0;
111919
+ return (await readdir29(dir)).length === 0;
112090
111920
  } catch {
112091
111921
  return false;
112092
111922
  }
@@ -112096,19 +111926,19 @@ async function pruneClaudeOrphanContextCache(dryRun, steps) {
112096
111926
  if (!existsSync34(cacheRoot))
112097
111927
  return;
112098
111928
  const removed = [];
112099
- const marketplaces = await readdir28(cacheRoot, { withFileTypes: true }).catch(() => []);
111929
+ const marketplaces = await readdir29(cacheRoot, { withFileTypes: true }).catch(() => []);
112100
111930
  for (const marketplace of marketplaces) {
112101
111931
  if (!marketplace.isDirectory())
112102
111932
  continue;
112103
- const pluginDir = join107(cacheRoot, marketplace.name, PLUGIN_NAME);
111933
+ const pluginDir = join109(cacheRoot, marketplace.name, PLUGIN_NAME);
112104
111934
  if (!existsSync34(pluginDir))
112105
111935
  continue;
112106
- const versions = await readdir28(pluginDir, { withFileTypes: true }).catch(() => []);
111936
+ const versions = await readdir29(pluginDir, { withFileTypes: true }).catch(() => []);
112107
111937
  for (const version3 of versions) {
112108
111938
  if (!version3.isDirectory())
112109
111939
  continue;
112110
- const versionDir = join107(pluginDir, version3.name);
112111
- if (!existsSync34(join107(versionDir, ORPHAN_MARKER2)))
111940
+ const versionDir = join109(pluginDir, version3.name);
111941
+ if (!existsSync34(join109(versionDir, ORPHAN_MARKER2)))
112112
111942
  continue;
112113
111943
  removed.push(`${marketplace.name}/${PLUGIN_NAME}/${version3.name}`);
112114
111944
  if (!dryRun) {
@@ -112118,7 +111948,7 @@ async function pruneClaudeOrphanContextCache(dryRun, steps) {
112118
111948
  if (!dryRun && await isEmptyDir2(pluginDir)) {
112119
111949
  await rm23(pluginDir, { recursive: true, force: true });
112120
111950
  }
112121
- const marketplaceDir = join107(cacheRoot, marketplace.name);
111951
+ const marketplaceDir = join109(cacheRoot, marketplace.name);
112122
111952
  if (!dryRun && await isEmptyDir2(marketplaceDir)) {
112123
111953
  await rm23(marketplaceDir, { recursive: true, force: true });
112124
111954
  }
@@ -112139,7 +111969,7 @@ async function pruneClaudeLegacyPluginCache(dryRun, steps) {
112139
111969
  return;
112140
111970
  const removed = [];
112141
111971
  for (const pluginName of LEGACY_PLUGIN_NAMES) {
112142
- const pluginDir = join107(cacheRoot, MARKETPLACE_NAME, pluginName);
111972
+ const pluginDir = join109(cacheRoot, MARKETPLACE_NAME, pluginName);
112143
111973
  if (!existsSync34(pluginDir))
112144
111974
  continue;
112145
111975
  removed.push(`${MARKETPLACE_NAME}/${pluginName}`);
@@ -112156,12 +111986,12 @@ async function pruneClaudeLegacyPluginCache(dryRun, steps) {
112156
111986
  }
112157
111987
  }
112158
111988
  async function pruneClaudeSupersededContextCache(root2, dryRun, steps) {
112159
- const manifest = await readFile89(join107(root2, "claude", ".claude-plugin", "plugin.json"), "utf8").then((content3) => JSON.parse(content3)).catch(() => null);
111989
+ const manifest = await readFile91(join109(root2, "claude", ".claude-plugin", "plugin.json"), "utf8").then((content3) => JSON.parse(content3)).catch(() => null);
112160
111990
  const currentVersion = typeof manifest?.version === "string" ? manifest.version : null;
112161
111991
  if (!currentVersion)
112162
111992
  return;
112163
- const pluginDir = join107(claudePluginCacheRoot(), MARKETPLACE_NAME, PLUGIN_NAME);
112164
- const staleVersions = (await readdir28(pluginDir, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory() && entry.name !== currentVersion).map((entry) => entry.name);
111993
+ const pluginDir = join109(claudePluginCacheRoot(), MARKETPLACE_NAME, PLUGIN_NAME);
111994
+ const staleVersions = (await readdir29(pluginDir, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory() && entry.name !== currentVersion).map((entry) => entry.name);
112165
111995
  if (staleVersions.length === 0)
112166
111996
  return;
112167
111997
  steps.push({
@@ -112170,7 +112000,7 @@ async function pruneClaudeSupersededContextCache(root2, dryRun, steps) {
112170
112000
  status: dryRun ? "planned" : "ran"
112171
112001
  });
112172
112002
  if (!dryRun) {
112173
- await Promise.all(staleVersions.map((version3) => rm23(join107(pluginDir, version3), { recursive: true, force: true })));
112003
+ await Promise.all(staleVersions.map((version3) => rm23(join109(pluginDir, version3), { recursive: true, force: true })));
112174
112004
  }
112175
112005
  }
112176
112006
  function enableCodexPluginConfig(content3) {
@@ -112234,36 +112064,36 @@ source = ${JSON.stringify(root2)}
112234
112064
  `;
112235
112065
  }
112236
112066
  async function ensureCodexPluginEnabled() {
112237
- const configPath = join107(codexHome(), "config.toml");
112238
- await mkdir35(dirname46(configPath), { recursive: true });
112239
- const current2 = await readFile89(configPath, "utf8").catch(() => "");
112067
+ const configPath = join109(codexHome(), "config.toml");
112068
+ await mkdir35(dirname45(configPath), { recursive: true });
112069
+ const current2 = await readFile91(configPath, "utf8").catch(() => "");
112240
112070
  const next2 = enableCodexPluginConfig(current2);
112241
112071
  if (next2 !== current2) {
112242
112072
  await writeFile27(configPath, next2, "utf8");
112243
112073
  }
112244
112074
  }
112245
112075
  async function ensureCodexLocalMarketplace(root2) {
112246
- const configPath = join107(codexHome(), "config.toml");
112247
- await mkdir35(dirname46(configPath), { recursive: true });
112248
- const current2 = await readFile89(configPath, "utf8").catch(() => "");
112076
+ const configPath = join109(codexHome(), "config.toml");
112077
+ await mkdir35(dirname45(configPath), { recursive: true });
112078
+ const current2 = await readFile91(configPath, "utf8").catch(() => "");
112249
112079
  const next2 = upsertCodexLocalMarketplaceConfig(current2, root2);
112250
112080
  if (next2 !== current2) {
112251
112081
  await writeFile27(configPath, next2, "utf8");
112252
112082
  }
112253
112083
  }
112254
112084
  async function codexPluginVersion(root2) {
112255
- const manifestPath = join107(root2, "codex", ".codex-plugin", "plugin.json");
112256
- const manifest = JSON.parse(await readFile89(manifestPath, "utf8"));
112085
+ const manifestPath = join109(root2, "codex", ".codex-plugin", "plugin.json");
112086
+ const manifest = JSON.parse(await readFile91(manifestPath, "utf8"));
112257
112087
  if (typeof manifest.version !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/u.test(manifest.version)) {
112258
112088
  throw new Error(`Codex plugin manifest has an invalid version: ${manifestPath}`);
112259
112089
  }
112260
112090
  return manifest.version;
112261
112091
  }
112262
112092
  function codexPluginCacheDir(version3) {
112263
- return join107(codexHome(), "plugins", "cache", MARKETPLACE_NAME, PLUGIN_NAME, version3);
112093
+ return join109(codexHome(), "plugins", "cache", MARKETPLACE_NAME, PLUGIN_NAME, version3);
112264
112094
  }
112265
112095
  async function replaceDirectoryFromSource(source2, target) {
112266
- await mkdir35(dirname46(target), { recursive: true });
112096
+ await mkdir35(dirname45(target), { recursive: true });
112267
112097
  const temporary = `${target}.tmp-${process.pid}-${Date.now()}`;
112268
112098
  const previous3 = `${target}.previous-${process.pid}-${Date.now()}`;
112269
112099
  await rm23(temporary, { recursive: true, force: true });
@@ -112283,7 +112113,7 @@ async function replaceDirectoryFromSource(source2, target) {
112283
112113
  }
112284
112114
  }
112285
112115
  async function materializeCodexPluginCache(root2, version3, dryRun, steps) {
112286
- const source2 = join107(root2, "codex");
112116
+ const source2 = join109(root2, "codex");
112287
112117
  const target = codexPluginCacheDir(version3);
112288
112118
  steps.push({
112289
112119
  agent: "codex",
@@ -112295,16 +112125,16 @@ async function materializeCodexPluginCache(root2, version3, dryRun, steps) {
112295
112125
  await replaceDirectoryFromSource(source2, target);
112296
112126
  }
112297
112127
  async function bundledProviderSkillNames(root2) {
112298
- const skillsRoot = join107(root2, "skills");
112299
- const entries2 = await readdir28(skillsRoot, { withFileTypes: true });
112128
+ const skillsRoot = join109(root2, "skills");
112129
+ const entries2 = await readdir29(skillsRoot, { withFileTypes: true });
112300
112130
  const names = [];
112301
112131
  for (const entry of entries2) {
112302
112132
  if (!entry.isDirectory() || entry.name === "context")
112303
112133
  continue;
112304
- const skillPath = join107(skillsRoot, entry.name, "SKILL.md");
112134
+ const skillPath = join109(skillsRoot, entry.name, "SKILL.md");
112305
112135
  if (!existsSync34(skillPath))
112306
112136
  continue;
112307
- const skill = await readFile89(skillPath, "utf8");
112137
+ const skill = await readFile91(skillPath, "utf8");
112308
112138
  if (!/^\s*context-role:\s*["']?indexer-provider["']?\s*$/mu.test(skill))
112309
112139
  continue;
112310
112140
  names.push(entry.name);
@@ -112316,10 +112146,10 @@ async function bundledProviderSkillNames(root2) {
112316
112146
  return names;
112317
112147
  }
112318
112148
  async function materializeProviderSkills(root2, targetRoot, agent, dryRun, steps) {
112319
- const sourceRoot2 = join107(root2, "skills");
112149
+ const sourceRoot2 = join109(root2, "skills");
112320
112150
  for (const name3 of await bundledProviderSkillNames(root2)) {
112321
- const source2 = join107(sourceRoot2, name3);
112322
- const target = join107(targetRoot, name3);
112151
+ const source2 = join109(sourceRoot2, name3);
112152
+ const target = join109(targetRoot, name3);
112323
112153
  steps.push({
112324
112154
  agent,
112325
112155
  command: `materialize lifecycle Provider skill: ${shellQuote8(source2)} -> ${shellQuote8(target)}`,
@@ -112330,7 +112160,7 @@ async function materializeProviderSkills(root2, targetRoot, agent, dryRun, steps
112330
112160
  }
112331
112161
  }
112332
112162
  async function installCursor(root2, dryRun, steps) {
112333
- const source2 = join107(root2, "cursor");
112163
+ const source2 = join109(root2, "cursor");
112334
112164
  const target = cursorPluginRoot();
112335
112165
  steps.push({
112336
112166
  agent: "cursor",
@@ -112385,12 +112215,12 @@ async function installCodex(root2, dryRun, steps) {
112385
112215
  steps.push({ agent: "codex", command: commandLine("codex", addArgs), status: dryRun ? "planned" : "ran" });
112386
112216
  steps.push({
112387
112217
  agent: "codex",
112388
- command: `ensure ${shellQuote8(join107(codexHome(), "config.toml"))} registers local marketplace ${shellQuote8(MARKETPLACE_NAME)}`,
112218
+ command: `ensure ${shellQuote8(join109(codexHome(), "config.toml"))} registers local marketplace ${shellQuote8(MARKETPLACE_NAME)}`,
112389
112219
  status: dryRun ? "planned" : "ran"
112390
112220
  });
112391
112221
  steps.push({
112392
112222
  agent: "codex",
112393
- command: `ensure ${shellQuote8(join107(codexHome(), "config.toml"))} enables ${shellQuote8(PLUGIN_ID)}`,
112223
+ command: `ensure ${shellQuote8(join109(codexHome(), "config.toml"))} enables ${shellQuote8(PLUGIN_ID)}`,
112394
112224
  status: dryRun ? "planned" : "ran"
112395
112225
  });
112396
112226
  if (dryRun) {
@@ -112429,10 +112259,10 @@ function pluginAgentOption(value) {
112429
112259
  }
112430
112260
  function packageCandidateDirs() {
112431
112261
  const dirs = [];
112432
- let dir = dirname47(fileURLToPath9(import.meta.url));
112262
+ let dir = dirname46(fileURLToPath9(import.meta.url));
112433
112263
  for (let index2 = 0;index2 < 8; index2++) {
112434
112264
  dirs.push(dir);
112435
- const parent = dirname47(dir);
112265
+ const parent = dirname46(dir);
112436
112266
  if (parent === dir)
112437
112267
  break;
112438
112268
  dir = parent;
@@ -112445,13 +112275,13 @@ function pluginRootCandidates() {
112445
112275
  return [resolve40(envRoot)];
112446
112276
  const candidates = [];
112447
112277
  for (const dir of packageCandidateDirs()) {
112448
- candidates.push(join108(dir, "plugins"));
112449
- candidates.push(join108(dir, "dist", "plugins"));
112278
+ candidates.push(join110(dir, "plugins"));
112279
+ candidates.push(join110(dir, "dist", "plugins"));
112450
112280
  }
112451
112281
  return [...new Set(candidates)];
112452
112282
  }
112453
112283
  function isInstallablePluginRoot(root2) {
112454
- return existsSync35(join108(root2, ".claude-plugin", "marketplace.json")) && existsSync35(join108(root2, ".agents", "plugins", "marketplace.json")) && existsSync35(join108(root2, "claude", ".claude-plugin", "plugin.json")) && existsSync35(join108(root2, "codex", ".codex-plugin", "plugin.json")) && existsSync35(join108(root2, "cursor", ".cursor-plugin", "plugin.json")) && existsSync35(join108(root2, "skills"));
112284
+ return existsSync35(join110(root2, ".claude-plugin", "marketplace.json")) && existsSync35(join110(root2, ".agents", "plugins", "marketplace.json")) && existsSync35(join110(root2, "claude", ".claude-plugin", "plugin.json")) && existsSync35(join110(root2, "codex", ".codex-plugin", "plugin.json")) && existsSync35(join110(root2, "cursor", ".cursor-plugin", "plugin.json")) && existsSync35(join110(root2, "skills"));
112455
112285
  }
112456
112286
  function resolveBundledPluginsRoot() {
112457
112287
  const candidates = pluginRootCandidates();
@@ -112562,32 +112392,32 @@ function formatPluginStatusResult(result) {
112562
112392
  function formatPluginInstallResult(result) {
112563
112393
  const degraded = result.results.some((item) => item.status === "skipped" || item.status === "failed");
112564
112394
  const ready = result.results.filter((item) => item.status === "installed" || item.status === "planned").map((item) => item.agent);
112565
- const body = [
112395
+ const body2 = [
112566
112396
  `marketplace: ${dim(result.pluginsRoot)}`,
112567
112397
  "manual install: use the marketplace path above as the plugin marketplace root."
112568
112398
  ];
112569
112399
  for (const item of result.results) {
112570
112400
  if (item.status === "installed" || item.status === "planned") {
112571
- body.push(`✅ ${item.agent}: ${item.status}`);
112401
+ body2.push(`✅ ${item.agent}: ${item.status}`);
112572
112402
  continue;
112573
112403
  }
112574
112404
  const icon = item.status === "failed" ? "✗" : "⚠";
112575
112405
  const detail = item.message ? ` — ${item.message}` : "";
112576
- body.push(yellow(`${icon} ${item.agent}: ${item.status}${detail}`));
112406
+ body2.push(yellow(`${icon} ${item.agent}: ${item.status}${detail}`));
112577
112407
  if (item.next)
112578
- body.push(yellow(` next: ${item.next}`));
112408
+ body2.push(yellow(` next: ${item.next}`));
112579
112409
  }
112580
112410
  const detailSteps = result.steps.map((step) => ` ${step.agent}: ${step.status} ${step.command}`);
112581
112411
  if (detailSteps.length > 0) {
112582
- body.push("details:");
112583
- body.push(...detailSteps.map(dim));
112412
+ body2.push("details:");
112413
+ body2.push(...detailSteps.map(dim));
112584
112414
  }
112585
112415
  return formatFeedback({
112586
112416
  symbol: result.dryRun ? "·" : degraded ? "⚠" : "✓",
112587
112417
  action: result.dryRun ? "planned" : "installed",
112588
112418
  subject: "context plugin",
112589
112419
  headline: `${ready.length}/${result.agents.length} target(s) ready`,
112590
- body
112420
+ body: body2
112591
112421
  });
112592
112422
  }
112593
112423
 
@@ -112687,29 +112517,29 @@ function inferErrorCategory(message) {
112687
112517
  }
112688
112518
  function readQuickstartPath() {
112689
112519
  try {
112690
- let dir = dirname48(fileURLToPath10(import.meta.url));
112520
+ let dir = dirname47(fileURLToPath10(import.meta.url));
112691
112521
  for (let i2 = 0;i2 < 8; i2++) {
112692
- const candidate = join109(dir, "docs", "quickstart.md");
112522
+ const candidate = join111(dir, "docs", "quickstart.md");
112693
112523
  if (existsSync36(candidate))
112694
112524
  return candidate;
112695
- const pkg = join109(dir, "package.json");
112525
+ const pkg = join111(dir, "package.json");
112696
112526
  if (existsSync36(pkg))
112697
112527
  return candidate;
112698
- const parent = dirname48(dir);
112528
+ const parent = dirname47(dir);
112699
112529
  if (parent === dir)
112700
112530
  break;
112701
112531
  dir = parent;
112702
112532
  }
112703
112533
  } catch {}
112704
- return join109(dirname48(fileURLToPath10(import.meta.url)), "docs", "quickstart.md");
112534
+ return join111(dirname47(fileURLToPath10(import.meta.url)), "docs", "quickstart.md");
112705
112535
  }
112706
112536
  var GREEN = "\x1B[32m";
112707
112537
  var RESET = "\x1B[0m";
112708
112538
  function greenBox(lines) {
112709
112539
  const width = Math.max(...lines.map((line) => line.length));
112710
112540
  const border = `+${"-".repeat(width + 2)}+`;
112711
- const body = lines.map((line) => `| ${line.padEnd(width)} |`);
112712
- return `${GREEN}${[border, ...body, border].join(`
112541
+ const body2 = lines.map((line) => `| ${line.padEnd(width)} |`);
112542
+ return `${GREEN}${[border, ...body2, border].join(`
112713
112543
  `)}${RESET}`;
112714
112544
  }
112715
112545
  function headerHelpText() {